@equinor/fusion-framework-vite-plugin-spa 4.0.12 → 4.0.14

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/esm/html/bootstrap.js +3 -2
  3. package/dist/esm/html/bootstrap.js.map +1 -1
  4. package/dist/esm/html/create-portal-entry-point.js +5 -0
  5. package/dist/esm/html/create-portal-entry-point.js.map +1 -1
  6. package/dist/esm/html/{index.html.js → html.js} +1 -1
  7. package/dist/esm/html/html.js.map +1 -0
  8. package/dist/esm/html/register-service-worker.js +10 -0
  9. package/dist/esm/html/register-service-worker.js.map +1 -1
  10. package/dist/esm/html/sw.js +6 -0
  11. package/dist/esm/html/sw.js.map +1 -1
  12. package/dist/esm/plugin.js +4 -2
  13. package/dist/esm/plugin.js.map +1 -1
  14. package/dist/esm/util/{load-env.js → load-environment.js} +1 -1
  15. package/dist/esm/util/load-environment.js.map +1 -0
  16. package/dist/esm/util/object-to-env.js +2 -0
  17. package/dist/esm/util/object-to-env.js.map +1 -1
  18. package/dist/esm/version.js +1 -1
  19. package/dist/html/bootstrap.js +724 -196
  20. package/dist/html/bootstrap.js.map +1 -1
  21. package/dist/html/sw.js +6 -0
  22. package/dist/html/sw.js.map +1 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/dist/types/html/{index.html.d.ts → html.d.ts} +1 -1
  25. package/dist/types/version.d.ts +1 -1
  26. package/package.json +7 -7
  27. package/src/html/bootstrap.ts +4 -3
  28. package/src/html/create-portal-entry-point.ts +5 -0
  29. package/src/html/register-service-worker.ts +10 -0
  30. package/src/html/sw.ts +6 -0
  31. package/src/plugin.ts +4 -2
  32. package/src/util/object-to-env.ts +2 -0
  33. package/src/version.ts +1 -1
  34. package/dist/esm/html/index.html.js.map +0 -1
  35. package/dist/esm/util/load-env.js.map +0 -1
  36. /package/dist/types/util/{load-env.d.ts → load-environment.d.ts} +0 -0
  37. /package/src/html/{index.html.ts → html.ts} +0 -0
  38. /package/src/util/{load-env.ts → load-environment.ts} +0 -0
@@ -5093,6 +5093,7 @@ class BaseConfigBuilder {
5093
5093
  * If a function is provided as `value_or_cb`, it will be used as a callback for deferred or computed configuration values.
5094
5094
  * Otherwise, the value is wrapped in an async function for consistency.
5095
5095
  *
5096
+ * @template TTarget - The dot-path string representing the target property in the configuration.
5096
5097
  * @protected
5097
5098
  * @sealed
5098
5099
  */
@@ -5105,6 +5106,7 @@ class BaseConfigBuilder {
5105
5106
  *
5106
5107
  * @param target - The target path in the configuration to retrieve the callback for.
5107
5108
  * @returns The configuration builder callback for the specified target, or `undefined` if no callback is registered.
5109
+ * @template TTarget - The dot-path string representing the target property in the configuration.
5108
5110
  * @protected
5109
5111
  * @sealed
5110
5112
  */
@@ -5115,6 +5117,7 @@ class BaseConfigBuilder {
5115
5117
  * Checks if the given target path exists in the configuration callbacks.
5116
5118
  * @param target - The target path to check.
5117
5119
  * @returns `true` if the target path exists in the configuration callbacks, `false` otherwise.
5120
+ * @template TTarget - The dot-path string representing the target property in the configuration.
5118
5121
  * @protected
5119
5122
  * @sealed
5120
5123
  */
@@ -5168,9 +5171,12 @@ class BaseConfigBuilder {
5168
5171
  * @readonly
5169
5172
  */
5170
5173
  _buildConfig(init, initial) {
5174
+ // Execute every registered config callback and collect its target-value pair
5171
5175
  return from(Object.entries(this.#configCallbacks)).pipe(
5172
5176
  // Transform each config callback into a target-value pair
5173
- mergeMap(([target, cb]) => from(cb(init)).pipe(
5177
+ mergeMap(([target, cb]) =>
5178
+ // Run the callback and shape its result into a target-value pair, skipping void results
5179
+ from(cb(init)).pipe(
5174
5180
  // Filter out undefined values, mostly for void return types
5175
5181
  filter((value) => value !== undefined),
5176
5182
  // Map the value to a target-value pair
@@ -5225,6 +5231,9 @@ class BaseConfigBuilder {
5225
5231
  * Indicates a circular dependency or a module that never resolves.
5226
5232
  */
5227
5233
  class RequiredModuleTimeoutError extends Error {
5234
+ /**
5235
+ * Creates a new `RequiredModuleTimeoutError`.
5236
+ */
5228
5237
  constructor() {
5229
5238
  super('Module initialization timed out');
5230
5239
  this.name = 'RequiredModuleTimeoutError';
@@ -5233,6 +5242,7 @@ class RequiredModuleTimeoutError extends Error {
5233
5242
 
5234
5243
  /** Shared base segment for module configurator lifecycle event names. */
5235
5244
  const ModuleConfiguratorEventBaseName = 'ModuleConfigurator';
5245
+
5236
5246
  /**
5237
5247
  * Event names emitted by `ModulesConfigurator` and its lifecycle phase helpers.
5238
5248
  *
@@ -5291,7 +5301,6 @@ const ModuleConfiguratorEventName = {
5291
5301
  ModulesDisposed: `${ModuleConfiguratorEventBaseName}.modules.disposed`,
5292
5302
  };
5293
5303
 
5294
- /* eslint-disable @typescript-eslint/no-explicit-any */
5295
5304
  /**
5296
5305
  * Defines the severity levels for module events.
5297
5306
  * Used to categorize events by their importance and impact.
@@ -5324,10 +5333,13 @@ var ModuleEventLevel;
5324
5333
  * @param ctx - The configure phase context.
5325
5334
  * @param ref - Optional reference forwarded to each module's configure factory.
5326
5335
  * @returns A promise resolving to the merged module config map.
5336
+ * @template TRef - Reference type forwarded to module configure hooks.
5337
+ * @throws Re-throws any error from a module's `configure` factory after emitting a failure event.
5327
5338
  * @internal
5328
5339
  */
5329
5340
  async function createModuleConfigs(ctx, ref) {
5330
5341
  const { modules, afterConfiguration, afterInit, registerEvent } = ctx;
5342
+ // Run each module's configure factory concurrently and track timing/failures
5331
5343
  const config$ = from(modules).pipe(mergeMap(async (module) => {
5332
5344
  const configStart = performance.now();
5333
5345
  try {
@@ -5360,7 +5372,11 @@ async function createModuleConfigs(ctx, ref) {
5360
5372
  });
5361
5373
  throw err;
5362
5374
  }
5363
- }), reduce((acc, module) => Object.assign(acc, module),
5375
+ }), reduce((acc, module) => {
5376
+ // Merge each module's config object into the shared accumulator
5377
+ const merged = Object.assign(acc, module);
5378
+ return merged;
5379
+ },
5364
5380
  // Seed the config object with hooks so modules can register post-phase callbacks
5365
5381
  // during their own configure factory (a common pattern for cross-module wiring).
5366
5382
  {
@@ -5373,6 +5389,7 @@ async function createModuleConfigs(ctx, ref) {
5373
5389
  }));
5374
5390
  return lastValueFrom(config$);
5375
5391
  }
5392
+
5376
5393
  /**
5377
5394
  * Runs the post-configure phase: calls each module's `postConfigure` hook and
5378
5395
  * then invokes all registered `afterConfiguration` callbacks.
@@ -5383,14 +5400,19 @@ async function createModuleConfigs(ctx, ref) {
5383
5400
  * @param ctx - The configure phase context.
5384
5401
  * @param config - The merged module config map produced by {@link createModuleConfigs}.
5385
5402
  * @returns A promise resolving when all post-configure hooks have settled.
5403
+ * @template TRef - Reference type forwarded to module configure hooks.
5386
5404
  * @internal
5387
5405
  */
5388
- async function runPostConfigureHooks(ctx, config) {
5406
+ async function runPostConfigureHooks(ctx,
5407
+ // biome-ignore lint/suspicious/noExplicitAny: internal type-erased dispatch — the configure phase coordinates opaque module configs without knowing their concrete shapes
5408
+ config) {
5389
5409
  const { modules, afterConfiguration, registerEvent } = ctx;
5390
5410
  // Run each module's postConfigure hook; failures are isolated so one
5391
5411
  // failing module does not prevent others from completing post-configure.
5392
5412
  await Promise.allSettled(modules
5413
+ // Only process modules that define a postConfigure hook
5393
5414
  .filter((module) => !!module.postConfigure)
5415
+ // Call postConfigure on each module that defines the hook
5394
5416
  .map(async (module) => {
5395
5417
  try {
5396
5418
  const postConfigStart = performance.now();
@@ -5419,6 +5441,7 @@ async function runPostConfigureHooks(ctx, config) {
5419
5441
  });
5420
5442
  }
5421
5443
  }));
5444
+ // Skip the afterConfiguration hooks entirely when none are registered
5422
5445
  if (!afterConfiguration.length)
5423
5446
  return;
5424
5447
  // Run all registered afterConfiguration callbacks. These were added either
@@ -5431,7 +5454,9 @@ async function runPostConfigureHooks(ctx, config) {
5431
5454
  message: `Post configure hooks [${afterConfiguration.length}] called`,
5432
5455
  });
5433
5456
  const postConfigHooksStart = performance.now();
5434
- await Promise.allSettled(afterConfiguration.map((x) => Promise.resolve(x(config))));
5457
+ await Promise.allSettled(afterConfiguration
5458
+ // Invoke every registered afterConfiguration callback with the final config
5459
+ .map((x) => Promise.resolve(x(config))));
5435
5460
  const postConfigHooksTime = Math.round(performance.now() - postConfigHooksStart);
5436
5461
  registerEvent({
5437
5462
  level: ModuleEventLevel.Debug,
@@ -5453,6 +5478,7 @@ async function runPostConfigureHooks(ctx, config) {
5453
5478
  });
5454
5479
  }
5455
5480
  }
5481
+
5456
5482
  /**
5457
5483
  * Runs the full configure lifecycle phase for a set of modules.
5458
5484
  *
@@ -5464,12 +5490,15 @@ async function runPostConfigureHooks(ctx, config) {
5464
5490
  * @param ctx - The configure phase context.
5465
5491
  * @param ref - Optional reference forwarded through all configure hooks.
5466
5492
  * @returns A promise resolving to the fully configured module config map.
5493
+ * @template TRef - Reference type forwarded to module configure hooks.
5467
5494
  */
5468
5495
  async function runConfigurePhase(ctx, ref) {
5469
5496
  // Step 1: Create raw config objects for all registered modules
5470
5497
  const config = await createModuleConfigs(ctx, ref);
5471
5498
  // Step 2: Apply all user-registered configuration callbacks concurrently.
5472
- await Promise.all(ctx.configs.map((cb) => Promise.resolve(cb(config, ref))));
5499
+ await Promise.all(
5500
+ // Invoke every registered configure callback with the merged config
5501
+ ctx.configs.map((cb) => Promise.resolve(cb(config, ref))));
5473
5502
  // Step 3: Run module postConfigure hooks and afterConfiguration callbacks
5474
5503
  await runPostConfigureHooks(ctx, config);
5475
5504
  return config;
@@ -5519,6 +5548,7 @@ class BaseModuleProvider {
5519
5548
  // where inherited properties are acceptable for instanceof checks
5520
5549
  'version' in instance &&
5521
5550
  'dispose' in instance;
5551
+ // Only coerce version/dispose once the basic shape check has passed
5522
5552
  if (hasStructure) {
5523
5553
  const obj = instance;
5524
5554
  const version = semverExports.coerce(String(obj.version));
@@ -5529,6 +5559,11 @@ class BaseModuleProvider {
5529
5559
  }
5530
5560
  #version;
5531
5561
  #subscriptions;
5562
+ /**
5563
+ * The resolved semantic version of this module provider.
5564
+ *
5565
+ * @returns The provider's semantic version.
5566
+ */
5532
5567
  get version() {
5533
5568
  return this.#version;
5534
5569
  }
@@ -5562,92 +5597,6 @@ class BaseModuleProvider {
5562
5597
  }
5563
5598
  }
5564
5599
 
5565
- /**
5566
- * Initializes a single module and emits a `[name, instance]` tuple when complete.
5567
- *
5568
- * Validates that the module exposes an `initialize` method, calls it with the
5569
- * provided context, and emits lifecycle events for start, completion, and any
5570
- * provider contract warnings.
5571
- *
5572
- * @internal
5573
- */
5574
- async function initializeModule(module, ctx) {
5575
- const { config, ref, requireInstance, hasModule, registerEvent } = ctx;
5576
- const key = module.name;
5577
- if (!module.initialize) {
5578
- const error = new Error(`Module ${module.name} does not have initialize method`);
5579
- error.name = 'ModuleInitializeError';
5580
- registerEvent({
5581
- level: ModuleEventLevel.Error,
5582
- name: ModuleConfiguratorEventName.ModuleInitializeError,
5583
- message: error.message,
5584
- properties: {
5585
- moduleName: module.name,
5586
- moduleVersion: module.version?.toString() || 'unknown',
5587
- },
5588
- error,
5589
- });
5590
- throw error;
5591
- }
5592
- registerEvent({
5593
- level: ModuleEventLevel.Debug,
5594
- name: ModuleConfiguratorEventName.ModuleInitializing,
5595
- message: `Initializing module ${module.name}`,
5596
- properties: {
5597
- moduleName: module.name,
5598
- moduleVersion: module.version?.toString() || 'unknown',
5599
- },
5600
- });
5601
- const moduleInitStart = performance.now();
5602
- const instance = await module.initialize({
5603
- ref,
5604
- config: config[key],
5605
- requireInstance,
5606
- hasModule,
5607
- });
5608
- // Warn when providers deviate from the expected base class — these modules
5609
- // may lack version tracking and standard provider interfaces.
5610
- if (!(instance instanceof BaseModuleProvider)) {
5611
- registerEvent({
5612
- level: ModuleEventLevel.Warning,
5613
- name: ModuleConfiguratorEventName.ProviderNotBaseModuleProvider,
5614
- message: `Provider for module ${module.name} does not extend BaseModuleProvider`,
5615
- properties: {
5616
- moduleName: module.name,
5617
- moduleVersion: module.version?.toString() || 'unknown',
5618
- },
5619
- });
5620
- }
5621
- // Providers should expose a version string for diagnostics and compatibility checks.
5622
- // Warn when absent so module authors catch missing version early rather than at runtime.
5623
- const maybeVersioned = instance;
5624
- if (!maybeVersioned.version) {
5625
- registerEvent({
5626
- level: ModuleEventLevel.Warning,
5627
- name: ModuleConfiguratorEventName.ProviderVersionWarning,
5628
- message: `Provider for module ${module.name} does not expose version`,
5629
- properties: {
5630
- moduleName: module.name,
5631
- moduleVersion: module.version?.toString() || 'unknown',
5632
- },
5633
- });
5634
- }
5635
- const moduleInitTime = Math.round(performance.now() - moduleInitStart);
5636
- registerEvent({
5637
- level: ModuleEventLevel.Debug,
5638
- name: ModuleConfiguratorEventName.ModuleInitialized,
5639
- message: `Module ${module.name} initialized in ${moduleInitTime}ms`,
5640
- properties: {
5641
- moduleName: module.name,
5642
- moduleVersion: module.version?.toString() || 'unknown',
5643
- providerName: typeof instance,
5644
- providerVersion: maybeVersioned.version?.toString() || 'unknown',
5645
- moduleInitTime,
5646
- },
5647
- metric: moduleInitTime,
5648
- });
5649
- return [key, instance];
5650
- }
5651
5600
  /**
5652
5601
  * Creates a `requireInstance` resolver for use during module initialization.
5653
5602
  *
@@ -5660,6 +5609,10 @@ async function initializeModule(module, ctx) {
5660
5609
  * @param instance$ - Shared subject accumulating initialized module instances.
5661
5610
  * @param registerEvent - Function to emit lifecycle events.
5662
5611
  * @returns A `requireInstance` function matching the shape expected by `ModuleInitializerArgs`.
5612
+ * @template T - The shape of the accumulated module instance map.
5613
+ * @throws {Error} When the requested module name was never registered.
5614
+ * @throws {RequiredModuleTimeoutError} When the requested module does not
5615
+ * initialize within its timeout window.
5663
5616
  * @internal
5664
5617
  */
5665
5618
  function createRequireInstance(moduleNames, instance$, registerEvent) {
@@ -5734,6 +5687,95 @@ function createRequireInstance(moduleNames, instance$, registerEvent) {
5734
5687
  })));
5735
5688
  };
5736
5689
  }
5690
+
5691
+ /**
5692
+ * Initializes a single module and emits a `[name, instance]` tuple when complete.
5693
+ *
5694
+ * Validates that the module exposes an `initialize` method, calls it with the
5695
+ * provided context, and emits lifecycle events for start, completion, and any
5696
+ * provider contract warnings.
5697
+ *
5698
+ * @internal
5699
+ */
5700
+ async function initializeModule(module, ctx) {
5701
+ const { config, ref, requireInstance, hasModule, registerEvent } = ctx;
5702
+ const key = module.name;
5703
+ // Modules must expose an initialize method to participate in the initialize phase
5704
+ if (!module.initialize) {
5705
+ const error = new Error(`Module ${module.name} does not have initialize method`);
5706
+ error.name = 'ModuleInitializeError';
5707
+ registerEvent({
5708
+ level: ModuleEventLevel.Error,
5709
+ name: ModuleConfiguratorEventName.ModuleInitializeError,
5710
+ message: error.message,
5711
+ properties: {
5712
+ moduleName: module.name,
5713
+ moduleVersion: module.version?.toString() || 'unknown',
5714
+ },
5715
+ error,
5716
+ });
5717
+ throw error;
5718
+ }
5719
+ registerEvent({
5720
+ level: ModuleEventLevel.Debug,
5721
+ name: ModuleConfiguratorEventName.ModuleInitializing,
5722
+ message: `Initializing module ${module.name}`,
5723
+ properties: {
5724
+ moduleName: module.name,
5725
+ moduleVersion: module.version?.toString() || 'unknown',
5726
+ },
5727
+ });
5728
+ const moduleInitStart = performance.now();
5729
+ const instance = await module.initialize({
5730
+ ref,
5731
+ config: config[key],
5732
+ requireInstance,
5733
+ hasModule,
5734
+ });
5735
+ // Warn when providers deviate from the expected base class — these modules
5736
+ // may lack version tracking and standard provider interfaces.
5737
+ if (!(instance instanceof BaseModuleProvider)) {
5738
+ registerEvent({
5739
+ level: ModuleEventLevel.Warning,
5740
+ name: ModuleConfiguratorEventName.ProviderNotBaseModuleProvider,
5741
+ message: `Provider for module ${module.name} does not extend BaseModuleProvider`,
5742
+ properties: {
5743
+ moduleName: module.name,
5744
+ moduleVersion: module.version?.toString() || 'unknown',
5745
+ },
5746
+ });
5747
+ }
5748
+ // Providers should expose a version string for diagnostics and compatibility checks.
5749
+ // Warn when absent so module authors catch missing version early rather than at runtime.
5750
+ const maybeVersioned = instance;
5751
+ // Only warn about missing version metadata now that we know it's actually absent
5752
+ if (!maybeVersioned.version) {
5753
+ registerEvent({
5754
+ level: ModuleEventLevel.Warning,
5755
+ name: ModuleConfiguratorEventName.ProviderVersionWarning,
5756
+ message: `Provider for module ${module.name} does not expose version`,
5757
+ properties: {
5758
+ moduleName: module.name,
5759
+ moduleVersion: module.version?.toString() || 'unknown',
5760
+ },
5761
+ });
5762
+ }
5763
+ const moduleInitTime = Math.round(performance.now() - moduleInitStart);
5764
+ registerEvent({
5765
+ level: ModuleEventLevel.Debug,
5766
+ name: ModuleConfiguratorEventName.ModuleInitialized,
5767
+ message: `Module ${module.name} initialized in ${moduleInitTime}ms`,
5768
+ properties: {
5769
+ moduleName: module.name,
5770
+ moduleVersion: module.version?.toString() || 'unknown',
5771
+ providerName: typeof instance,
5772
+ providerVersion: maybeVersioned.version?.toString() || 'unknown',
5773
+ moduleInitTime,
5774
+ },
5775
+ metric: moduleInitTime,
5776
+ });
5777
+ return [key, instance];
5778
+ }
5737
5779
  /**
5738
5780
  * Runs the initialize lifecycle phase for all registered modules.
5739
5781
  *
@@ -5748,6 +5790,7 @@ function createRequireInstance(moduleNames, instance$, registerEvent) {
5748
5790
  * @param config - The merged module config map produced by the configure phase.
5749
5791
  * @param ref - Optional reference forwarded to each module's `initialize` call.
5750
5792
  * @returns A promise resolving to the sealed map of initialized module providers.
5793
+ * @template T - The shape of the resolved module instance map.
5751
5794
  * @throws {Error} When a module's `initialize` method is missing.
5752
5795
  * @throws {RequiredModuleTimeoutError} When a required dependency does not
5753
5796
  * initialize within its timeout window.
@@ -5759,6 +5802,7 @@ async function runInitializePhase(ctx, config, ref) {
5759
5802
  if (modules.length === 0) {
5760
5803
  return Object.seal({});
5761
5804
  }
5805
+ // Extract module names for dependency lookups before any module has initialized
5762
5806
  const moduleNames = modules.map((m) => m.name);
5763
5807
  // Accumulates initialized module providers; BehaviorSubject lets requireInstance
5764
5808
  // reactively wait for a dependency to appear without polling.
@@ -5773,7 +5817,9 @@ async function runInitializePhase(ctx, config, ref) {
5773
5817
  // Completing the subject signals that all modules are initialized.
5774
5818
  init$.subscribe({
5775
5819
  next: ([name, module]) => {
5776
- instance$.next(Object.assign(instance$.value, { [name]: module }));
5820
+ // Merge the newly initialized module into the shared instance map without dropping peers
5821
+ const nextInstance = Object.assign(instance$.value, { [name]: module });
5822
+ instance$.next(nextInstance);
5777
5823
  },
5778
5824
  error: (err) => {
5779
5825
  registerEvent({
@@ -5842,7 +5888,9 @@ async function runPostInitializePhase(ctx, instance, ref) {
5842
5888
  });
5843
5889
  // Run postInitialize hooks for modules that declare them. Failures are caught
5844
5890
  // per-module via catchError so one failure does not abort the others.
5845
- const postInitialize$ = from(modules).pipe(filter((module) => !!module.postInitialize), tap((module) => {
5891
+ const postInitialize$ = from(modules).pipe(
5892
+ // Only process modules that define a postInitialize hook
5893
+ filter((module) => !!module.postInitialize), tap((module) => {
5846
5894
  registerEvent({
5847
5895
  level: ModuleEventLevel.Debug,
5848
5896
  name: ModuleConfiguratorEventName.ModulePostInitializeStart,
@@ -5854,6 +5902,7 @@ async function runPostInitializePhase(ctx, instance, ref) {
5854
5902
  });
5855
5903
  }), mergeMap((module) => {
5856
5904
  const postInitStart = performance.now();
5905
+ // Emit completion/error events for this module's postInitialize call as it settles
5857
5906
  return from(module.postInitialize({
5858
5907
  ref,
5859
5908
  modules: instance,
@@ -5897,6 +5946,7 @@ async function runPostInitializePhase(ctx, instance, ref) {
5897
5946
  properties: { modules: Object.keys(instance).join(', '), postInitTime },
5898
5947
  metric: postInitTime,
5899
5948
  });
5949
+ // Skip the afterInit hooks entirely when none are registered
5900
5950
  if (!afterInit.length) {
5901
5951
  registerEvent({
5902
5952
  level: ModuleEventLevel.Debug,
@@ -5909,32 +5959,40 @@ async function runPostInitializePhase(ctx, instance, ref) {
5909
5959
  // Run all registered afterInit callbacks. These were added either by
5910
5960
  // addConfig's afterInit or via onInitialized on the configurator.
5911
5961
  try {
5962
+ // Build a display string of registered hook names for the start event
5963
+ // Extract each hook's name, falling back for anonymous functions
5964
+ const hookNames = afterInit.map((x) => x.name || 'anonymous').join(', ');
5912
5965
  registerEvent({
5913
5966
  level: ModuleEventLevel.Debug,
5914
5967
  name: ModuleConfiguratorEventName.PostInitializeHooks,
5915
5968
  message: `Executing post-initialize hooks [${afterInit.length}]`,
5916
- properties: { hooks: afterInit.map((x) => x.name || 'anonymous').join(', ') },
5969
+ properties: { hooks: hookNames },
5917
5970
  });
5918
5971
  const afterInitStart = performance.now();
5919
- await Promise.allSettled(afterInit.map((x) => Promise.resolve(x(instance))));
5972
+ await Promise.allSettled(afterInit
5973
+ // Invoke every registered afterInit callback with the initialized module instance map
5974
+ .map((x) => Promise.resolve(x(instance))));
5920
5975
  const afterInitTime = Math.round(performance.now() - afterInitStart);
5921
5976
  registerEvent({
5922
5977
  level: ModuleEventLevel.Debug,
5923
5978
  name: ModuleConfiguratorEventName.PostInitializeHooksComplete,
5924
5979
  message: `Post-initialize hooks completed in ${afterInitTime}ms`,
5925
5980
  properties: {
5926
- hooks: afterInit.map((x) => x.name || 'anonymous').join(', '),
5981
+ hooks: hookNames,
5927
5982
  afterInitTime,
5928
5983
  },
5929
5984
  metric: afterInitTime,
5930
5985
  });
5931
5986
  }
5932
5987
  catch (err) {
5988
+ // Build a display string of registered hook names for the error event
5989
+ // Extract each hook's name, falling back for anonymous functions
5990
+ const hookNames = afterInit.map((x) => x.name || 'anonymous').join(', ');
5933
5991
  registerEvent({
5934
5992
  level: ModuleEventLevel.Warning,
5935
5993
  name: ModuleConfiguratorEventName.PostInitializeHooksError,
5936
5994
  message: 'Post-initialize hooks failed',
5937
- properties: { hooks: afterInit.map((x) => x.name || 'anonymous').join(', ') },
5995
+ properties: { hooks: hookNames },
5938
5996
  error: err,
5939
5997
  });
5940
5998
  }
@@ -5984,9 +6042,11 @@ function isPluginTeardown(value) {
5984
6042
  * @param modules - The initialized module instance map.
5985
6043
  * @param ref - Optional reference forwarded from module initialization.
5986
6044
  * @returns A promise resolving when all plugin callbacks have settled.
6045
+ * @template TRef - Reference type forwarded to plugin callbacks.
5987
6046
  */
5988
6047
  async function runPluginPhase(ctx, modules, ref) {
5989
6048
  const { plugins, teardowns, registerEvent } = ctx;
6049
+ // Skip the plugin phase entirely when no plugins were registered
5990
6050
  if (!plugins.length)
5991
6051
  return;
5992
6052
  registerEvent({
@@ -5995,7 +6055,9 @@ async function runPluginPhase(ctx, modules, ref) {
5995
6055
  message: `Registering plugins [${plugins.length}]`,
5996
6056
  properties: { count: plugins.length },
5997
6057
  });
5998
- const pluginRegistrations = await Promise.all(plugins.map(async (plugin) => {
6058
+ const pluginRegistrations = await Promise.all(plugins
6059
+ // Run every plugin callback concurrently so one slow plugin cannot delay the others
6060
+ .map(async (plugin) => {
5999
6061
  const pluginStart = performance.now();
6000
6062
  const name = plugin.name || 'anonymous';
6001
6063
  try {
@@ -6021,7 +6083,9 @@ async function runPluginPhase(ctx, modules, ref) {
6021
6083
  return undefined;
6022
6084
  }
6023
6085
  }));
6086
+ // Queue every plugin teardown returned above so the dispose phase can invoke them later
6024
6087
  for (const teardown of pluginRegistrations) {
6088
+ // Only queue teardowns for plugins that actually returned one
6025
6089
  if (teardown) {
6026
6090
  teardowns.push(teardown);
6027
6091
  }
@@ -6038,6 +6102,7 @@ function getPluginTeardownName(teardown) {
6038
6102
  return typeof teardown === 'function' ? teardown.name || 'anonymous' : 'dispose';
6039
6103
  }
6040
6104
  async function runPluginTeardown(teardown) {
6105
+ // Function-shaped teardowns are called directly; object-shaped ones expose a dispose() method
6041
6106
  if (typeof teardown === 'function') {
6042
6107
  await teardown();
6043
6108
  return;
@@ -6065,6 +6130,7 @@ async function runDisposePhase(ctx, instance, ref) {
6065
6130
  message: 'Disposing modules instance',
6066
6131
  properties: { modules: Object.keys(instance).join(', ') },
6067
6132
  });
6133
+ // Only run the plugin teardown sequence when plugins were actually registered
6068
6134
  if (pluginTeardowns.length) {
6069
6135
  registerEvent({
6070
6136
  level: ModuleEventLevel.Debug,
@@ -6105,8 +6171,11 @@ async function runDisposePhase(ctx, instance, ref) {
6105
6171
  // Dispose all modules concurrently; failures are isolated per module so
6106
6172
  // one bad teardown cannot leave other modules in an inconsistent state.
6107
6173
  await Promise.allSettled(modules
6174
+ // Only process modules that define a dispose hook
6108
6175
  .filter((module) => !!module.dispose)
6176
+ // Dispose each module that defines the hook
6109
6177
  .map(async (module) => {
6178
+ // Narrow the type for TypeScript; the filter above already guarantees this
6110
6179
  if (!module.dispose)
6111
6180
  return;
6112
6181
  try {
@@ -6150,7 +6219,7 @@ async function runDisposePhase(ctx, instance, ref) {
6150
6219
  }
6151
6220
 
6152
6221
  // Generated by genversion.
6153
- const version$8 = '6.1.0';
6222
+ const version$8 = '6.1.1';
6154
6223
 
6155
6224
  // biome-ignore-all lint/suspicious/noExplicitAny: internal type-erased dispatch arrays — callbacks are registered with concrete module types but stored erased; the orchestrator never inspects these shapes itself
6156
6225
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -6204,6 +6273,11 @@ class ModulesConfigurator {
6204
6273
  * Preserved as a static string so minification cannot change it at runtime.
6205
6274
  */
6206
6275
  static className = 'ModulesConfigurator';
6276
+ /**
6277
+ * The current package version of the module configurator.
6278
+ *
6279
+ * @returns The semantic version string.
6280
+ */
6207
6281
  get version() {
6208
6282
  return version$8;
6209
6283
  }
@@ -6213,6 +6287,12 @@ class ModulesConfigurator {
6213
6287
  // configuration before telemetry is wired up.
6214
6288
  // Memory bound: ~24 KB at ~240 bytes/event × 100 events.
6215
6289
  #event$ = new ReplaySubject(100);
6290
+ /**
6291
+ * Stream of lifecycle events emitted while modules are configured, initialized,
6292
+ * and disposed.
6293
+ *
6294
+ * @returns An observable of {@link ModuleEvent} entries.
6295
+ */
6216
6296
  get event$() {
6217
6297
  return this.#event$.asObservable();
6218
6298
  }
@@ -6293,6 +6373,7 @@ class ModulesConfigurator {
6293
6373
  * @param configs - One or more module configurator descriptors.
6294
6374
  */
6295
6375
  configure(...configs) {
6376
+ // Delegate each descriptor to addConfig so registration logic stays in one place
6296
6377
  for (const x of configs) {
6297
6378
  this.addConfig(x);
6298
6379
  }
@@ -6306,6 +6387,7 @@ class ModulesConfigurator {
6306
6387
  *
6307
6388
  * @param config - The module configurator descriptor to register.
6308
6389
  * @template T - The module type being registered.
6390
+ * @template TConfig - The resolved configuration type for the module.
6309
6391
  */
6310
6392
  addConfig(config) {
6311
6393
  const { module, afterConfig, afterInit, configure } = config;
@@ -6325,8 +6407,10 @@ class ModulesConfigurator {
6325
6407
  // Register each optional callback into its corresponding lifecycle phase array
6326
6408
  if (configure)
6327
6409
  this._configs.push((cfg, ref) => configure(cfg[module.name], ref));
6410
+ // Register the afterConfig callback, if provided
6328
6411
  if (afterConfig)
6329
6412
  this._afterConfiguration.push((cfg) => afterConfig(cfg[module.name]));
6413
+ // Register the afterInit callback, if provided
6330
6414
  if (afterInit)
6331
6415
  this._afterInit.push((instances) => afterInit(instances[module.name]));
6332
6416
  }
@@ -6418,17 +6502,21 @@ class ModulesConfigurator {
6418
6502
  * @param ref - Optional reference forwarded to all module lifecycle hooks.
6419
6503
  * @returns A promise resolving to the sealed, initialized module instance.
6420
6504
  * @template T - Additional modules to merge into the instance type.
6505
+ * @template R - The reference type, narrowed to `TRef`.
6421
6506
  */
6422
6507
  async initialize(ref) {
6423
6508
  const configStart = performance.now();
6424
6509
  const config = await this._configure(ref);
6425
6510
  const configLoadTime = Math.round(performance.now() - configStart);
6511
+ // Build a comma-separated module name list for telemetry properties
6512
+ // Extract just the module names before joining into a display string
6513
+ const configModuleNames = this.modules.map((m) => m.name).join(', ');
6426
6514
  this._registerEvent({
6427
6515
  level: ModuleEventLevel.Debug,
6428
6516
  name: ModuleConfiguratorEventName.InitializeConfigLoaded,
6429
6517
  message: `Modules configured in ${configLoadTime}ms`,
6430
6518
  properties: {
6431
- modules: this.modules.map((m) => m.name).join(', '),
6519
+ modules: configModuleNames,
6432
6520
  count: this.modules.length,
6433
6521
  loadTime: configLoadTime,
6434
6522
  },
@@ -6437,24 +6525,30 @@ class ModulesConfigurator {
6437
6525
  const instanceStart = performance.now();
6438
6526
  const instance = await this._initialize(config, ref);
6439
6527
  const instanceLoadTime = Math.round(performance.now() - instanceStart);
6528
+ // Build a comma-separated module name list for telemetry properties
6529
+ // Extract just the module names before joining into a display string
6530
+ const instanceModuleNames = this.modules.map((m) => m.name).join(', ');
6440
6531
  this._registerEvent({
6441
6532
  level: ModuleEventLevel.Debug,
6442
6533
  name: ModuleConfiguratorEventName.InitializeInstanceInitialized,
6443
6534
  message: `Modules initialized in ${instanceLoadTime}ms`,
6444
6535
  properties: {
6445
- modules: this.modules.map((m) => m.name).join(', '),
6536
+ modules: instanceModuleNames,
6446
6537
  count: this.modules.length,
6447
6538
  loadTime: instanceLoadTime,
6448
6539
  },
6449
6540
  metric: instanceLoadTime,
6450
6541
  });
6451
6542
  const totalLoadTime = configLoadTime + instanceLoadTime;
6543
+ // Build a comma-separated module name list for telemetry properties
6544
+ // Extract just the module names before joining into a display string
6545
+ const totalModuleNames = this.modules.map((m) => m.name).join(', ');
6452
6546
  this._registerEvent({
6453
6547
  level: ModuleEventLevel.Information,
6454
6548
  name: ModuleConfiguratorEventName.Initialize,
6455
6549
  message: `initialize in ${totalLoadTime}ms`,
6456
6550
  properties: {
6457
- modules: this.modules.map((m) => m.name).join(', '),
6551
+ modules: totalModuleNames,
6458
6552
  configLoadTime,
6459
6553
  instanceLoadTime,
6460
6554
  totalLoadTime,
@@ -6462,6 +6556,8 @@ class ModulesConfigurator {
6462
6556
  metric: totalLoadTime,
6463
6557
  });
6464
6558
  await this._postInitialize(instance, ref);
6559
+ // `instance` is the freshly built module instance record, which is structurally compatible
6560
+ // with `ModulesInstance<TModules>` but not nominally assignable across the generic params.
6465
6561
  const modules = Object.seal(Object.assign({}, instance, {
6466
6562
  dispose: () => this.dispose(instance),
6467
6563
  }));
@@ -6496,6 +6592,8 @@ class ModulesConfigurator {
6496
6592
  *
6497
6593
  * @param ref - Optional reference forwarded to module configure factories.
6498
6594
  * @returns A promise resolving to the merged module config map.
6595
+ * @template T - Additional modules to merge into the instance type.
6596
+ * @template R - The reference type, narrowed to `TRef`.
6499
6597
  * @protected
6500
6598
  */
6501
6599
  async _configure(ref) {
@@ -6518,6 +6616,8 @@ class ModulesConfigurator {
6518
6616
  * @param config - The merged module config map from the configure phase.
6519
6617
  * @param ref - Optional reference forwarded to each module's `initialize` call.
6520
6618
  * @returns A promise resolving to the sealed map of initialized module providers.
6619
+ * @template T - Additional modules to merge into the instance type.
6620
+ * @template R - The reference type. Defaults to `TRef`.
6521
6621
  * @protected
6522
6622
  */
6523
6623
  async _initialize(config, ref) {
@@ -6536,6 +6636,8 @@ class ModulesConfigurator {
6536
6636
  *
6537
6637
  * @param instance - The sealed module instance from the initialize phase.
6538
6638
  * @param ref - Optional reference forwarded to each module's `postInitialize` call.
6639
+ * @template T - Additional modules to merge into the instance type.
6640
+ * @template R - The reference type. Defaults to `TRef`.
6539
6641
  * @protected
6540
6642
  */
6541
6643
  async _postInitialize(instance, ref) {
@@ -6555,6 +6657,8 @@ class ModulesConfigurator {
6555
6657
  *
6556
6658
  * @param instance - The sealed module instance from the initialize phase.
6557
6659
  * @param ref - Optional reference forwarded to each plugin callback.
6660
+ * @template T - Additional modules to merge into the instance type.
6661
+ * @template R - The reference type, narrowed to `TRef`.
6558
6662
  * @protected
6559
6663
  */
6560
6664
  async _registerPlugins(instance, ref) {
@@ -6612,6 +6716,7 @@ class ProcessOperators {
6612
6716
  * It can be either an instance of IProcessOperators<T> or a record of string keys and ProcessOperator<T> values.
6613
6717
  */
6614
6718
  constructor(operators) {
6719
+ // accept either a raw operators record or another IProcessOperators instance to clone from
6615
6720
  if (operators && 'operators' in operators) {
6616
6721
  this._operators = { ...operators.operators };
6617
6722
  }
@@ -6627,6 +6732,7 @@ class ProcessOperators {
6627
6732
  * @throws Error if an operator with the same key already exists.
6628
6733
  */
6629
6734
  add(key, operator) {
6735
+ // guard against silently overwriting an existing operator under the same key
6630
6736
  if (Object.keys(this._operators).includes(key))
6631
6737
  throw Error(`Operator [${key}] already defined`);
6632
6738
  return this.set(key, operator);
@@ -6670,6 +6776,7 @@ class ProcessOperators {
6670
6776
  if (!operators.length) {
6671
6777
  return of(request);
6672
6778
  }
6779
+ // feed the request through each operator sequentially, keeping the previous value when one returns void
6673
6780
  return from(Object.values(this._operators)).pipe(mergeScan(
6674
6781
  // resolve current operator and return result or previous if void
6675
6782
  (value, operator) => Promise.resolve(operator(value)).then((x) => x ?? value),
@@ -6714,7 +6821,7 @@ class HttpRequestHandler extends ProcessOperators {
6714
6821
  */
6715
6822
  setHeader(key, value) {
6716
6823
  const operator = requestOperatorHeader(key, value);
6717
- return this.set('header-' + key, operator);
6824
+ return this.set(`header-${key}`, operator);
6718
6825
  }
6719
6826
  }
6720
6827
 
@@ -22018,7 +22125,9 @@ const fetchRequestSchema = requestInitSchema.extend({
22018
22125
  const capitalizeRequestMethodOperator = (options) => (request) => {
22019
22126
  const { error, success, data } = requestMethodCasing().safeParse(request.method);
22020
22127
  request.method = success ? data : request.method?.toUpperCase();
22128
+ // surface schema validation issues as warnings when not running silently
22021
22129
  if (error && true) {
22130
+ // one warning per issue so callers can see exactly what failed to validate
22022
22131
  for (const e of error.issues) {
22023
22132
  console.warn(e.message);
22024
22133
  }
@@ -22047,6 +22156,7 @@ const requestValidationOperator = (options) => (request) => {
22047
22156
  return parse ? result : void 0;
22048
22157
  }
22049
22158
  catch (error) {
22159
+ // re-throw so callers relying on `parse` can react to validation failures directly
22050
22160
  if (parse) {
22051
22161
  throw error;
22052
22162
  }
@@ -22061,11 +22171,17 @@ const requestValidationOperator = (options) => (request) => {
22061
22171
  class HttpResponseError extends Error {
22062
22172
  response;
22063
22173
  static Name = 'HttpResponseError';
22174
+ /**
22175
+ * @param message - The error message.
22176
+ * @param response - The HTTP response associated with the error.
22177
+ * @param options - Additional error options.
22178
+ */
22064
22179
  constructor(message, response, options) {
22065
22180
  super(message, options);
22066
22181
  this.response = response;
22067
22182
  }
22068
22183
  }
22184
+
22069
22185
  /**
22070
22186
  * Represents an error that occurs when handling a JSON response in an HTTP request.
22071
22187
  * Extends the base `HttpResponseError` class.
@@ -22089,6 +22205,7 @@ class HttpJsonResponseError extends HttpResponseError {
22089
22205
  this.data = options?.data;
22090
22206
  }
22091
22207
  }
22208
+
22092
22209
  /**
22093
22210
  * Represents an error that occurs when handling a server-sent event (SSE) HTTP response.
22094
22211
  *
@@ -22112,6 +22229,15 @@ class ServerSentEventResponseError extends HttpResponseError {
22112
22229
  }
22113
22230
  }
22114
22231
 
22232
+ /**
22233
+ * Thrown when `createClient(name)` is called with an unknown client key.
22234
+ *
22235
+ * This is only used when the provided string is neither a registered client name
22236
+ * nor an absolute `http:` or `https:` URL.
22237
+ */
22238
+ class ClientNotFoundException extends Error {
22239
+ }
22240
+
22115
22241
  const defaultDataParser = (data) => {
22116
22242
  try {
22117
22243
  return JSON.parse(data);
@@ -22205,28 +22331,38 @@ async function* readStream(reader, options) {
22205
22331
  : [options.eventFilter]
22206
22332
  : null;
22207
22333
  const decoder = new TextDecoder();
22334
+ // keep reading chunks from the stream until the reader signals completion
22208
22335
  while (true) {
22209
22336
  const { done, value } = await reader.read();
22337
+ // the underlying stream has ended, nothing more to read
22210
22338
  if (done) {
22339
+ // exit the read loop rather than yielding any further events
22211
22340
  break;
22212
22341
  }
22213
22342
  const text = decoder.decode(value, { stream: true });
22214
22343
  const events = parseEvents(text, { dataParser: options?.dataParser });
22344
+ // a chunk may contain multiple complete SSE events, emit each in turn
22215
22345
  for (const event of events) {
22346
+ // a retry directive reconfigures the reconnection delay rather than being a data event
22216
22347
  if (event.retry) {
22217
22348
  await new Promise((resolve) => setTimeout(resolve, Number.parseInt(event.retry ?? '300', 10)));
22349
+ // nothing to yield for a retry-only event
22218
22350
  continue;
22219
22351
  }
22352
+ // heartbeat filtering is opt-in via the skipHeartbeats option
22220
22353
  if (skipHeartbeats) {
22221
22354
  // Skip comment-based heartbeats (no event, data, or id)
22222
22355
  if (!event.event && !event.data && !event.id) {
22356
+ // this event carries no payload, so treat it as a heartbeat and skip it
22223
22357
  continue;
22224
22358
  }
22225
22359
  // Skip named heartbeat events (e.g., event: heartbeat or event: ping)
22226
22360
  if (event.event && ['heartbeat', 'ping'].includes(event.event)) {
22361
+ // an explicitly named heartbeat/ping event should also be skipped
22227
22362
  continue;
22228
22363
  }
22229
22364
  }
22365
+ // only emit events that pass the caller-provided event type filter, if any
22230
22366
  if (!eventFilter || (event.event && eventFilter.includes(event.event))) {
22231
22367
  yield event;
22232
22368
  }
@@ -22246,21 +22382,26 @@ async function* readStream(reader, options) {
22246
22382
  */
22247
22383
  const createSseSelector = (options) => {
22248
22384
  return (response) => {
22385
+ // an unsuccessful HTTP status means there is no valid SSE stream to read
22249
22386
  if (!response.ok) {
22250
22387
  throw new ServerSentEventResponseError(`HTTP error! Status: ${response.status}`, response);
22251
22388
  }
22389
+ // a missing body means there is nothing to stream from
22252
22390
  if (!response.body) {
22253
22391
  throw new ServerSentEventResponseError('Response body is not readable', response);
22254
22392
  }
22393
+ // guard against consuming a non-SSE response as if it were an event stream
22255
22394
  if (!response.headers.get('Content-Type')?.includes('text/event-stream')) {
22256
22395
  throw new ServerSentEventResponseError('Response is not a text/event-stream', response);
22257
22396
  }
22258
22397
  const reader = response.body.getReader();
22259
- return from(readStream(reader, {
22398
+ return (from(readStream(reader, {
22260
22399
  dataParser: options?.dataParser,
22261
22400
  skipHeartbeats: options?.skipHeartbeats,
22262
22401
  eventFilter: options?.eventFilter,
22263
- })).pipe(
22402
+ }))
22403
+ // stop the stream on abort and always release the underlying reader when done
22404
+ .pipe(
22264
22405
  // Stop reading if the abort signal is triggered
22265
22406
  takeUntil(options?.abortSignal ? fromEvent(options.abortSignal, 'abort') : EMPTY), finalize$2(async () => {
22266
22407
  // cancel just in case of a pre-mature exit
@@ -22268,14 +22409,18 @@ const createSseSelector = (options) => {
22268
22409
  /** ignore cancellation errors */
22269
22410
  });
22270
22411
  reader.releaseLock();
22271
- }));
22412
+ })));
22272
22413
  };
22273
22414
  };
22274
22415
 
22275
22416
  /** @inheritdoc */
22276
22417
  class HttpClientConfigurator {
22277
22418
  _clients = {};
22278
- /** Gets a shallow clone of all named client configurations. */
22419
+ /**
22420
+ * Gets a shallow clone of all named client configurations.
22421
+ *
22422
+ * @returns A shallow clone of the named client configurations.
22423
+ */
22279
22424
  get clients() {
22280
22425
  return { ...this._clients };
22281
22426
  }
@@ -22303,6 +22448,8 @@ class HttpClientConfigurator {
22303
22448
  configureClient(name, args) {
22304
22449
  const argFn = typeof args === 'string' ? { baseUri: args } : args;
22305
22450
  const options = typeof argFn === 'function' ? { onCreate: argFn } : argFn;
22451
+ // `options` is `HttpClientOptions<T>` (this call's generic), but `_clients` is keyed by the
22452
+ // configurator's own `TClient` — callers are expected to keep the two in sync.
22306
22453
  this._clients[name] = {
22307
22454
  ...this._clients[name],
22308
22455
  ...options,
@@ -22312,16 +22459,8 @@ class HttpClientConfigurator {
22312
22459
  }
22313
22460
 
22314
22461
  // Generated by genversion.
22315
- const version$6 = '8.0.3';
22462
+ const version$6 = '8.0.4';
22316
22463
 
22317
- /**
22318
- * Thrown when `createClient(name)` is called with an unknown client key.
22319
- *
22320
- * This is only used when the provided string is neither a registered client name
22321
- * nor an absolute `http:` or `https:` URL.
22322
- */
22323
- class ClientNotFoundException extends Error {
22324
- }
22325
22464
  /** URL protocols accepted as valid ad-hoc base URIs. */
22326
22465
  const SUPPORTED_PROTOCOLS = ['http:', 'https:', 'ws:', 'wss:'];
22327
22466
  /**
@@ -22359,6 +22498,10 @@ class HttpClientProvider extends BaseModuleProvider {
22359
22498
  get defaultHttpRequestHandler() {
22360
22499
  return this.config.defaultHttpRequestHandler;
22361
22500
  }
22501
+ /**
22502
+ * Creates a new `HttpClientProvider`.
22503
+ * @param config - The configurator providing client definitions and defaults.
22504
+ */
22362
22505
  constructor(config) {
22363
22506
  super({
22364
22507
  version: version$6,
@@ -22395,6 +22538,7 @@ class HttpClientProvider extends BaseModuleProvider {
22395
22538
  const { baseUri, defaultScopes = [], onCreate, ctor = this.config.defaultHttpClientCtor, requestHandler = this.defaultHttpRequestHandler, responseHandler, } = config;
22396
22539
  const options = { requestHandler, responseHandler };
22397
22540
  const instance = new ctor(baseUri || '', options);
22541
+ // attach the resolved default scopes onto the instance without overwriting other own properties
22398
22542
  Object.assign(instance, { defaultScopes });
22399
22543
  onCreate?.(instance);
22400
22544
  return instance;
@@ -22402,6 +22546,7 @@ class HttpClientProvider extends BaseModuleProvider {
22402
22546
  /**
22403
22547
  * Creates a client instance and returns it as the requested custom client type.
22404
22548
  *
22549
+ * @template T - The custom `HttpClient` implementation type to cast the created instance to.
22405
22550
  * @param key - The key of the pre-configured HTTP client to create.
22406
22551
  * @returns The created HTTP client instance, cast to the specified type `T`.
22407
22552
  *
@@ -22412,6 +22557,8 @@ class HttpClientProvider extends BaseModuleProvider {
22412
22557
  * is configured to use a different implementation.
22413
22558
  */
22414
22559
  createCustomClient(key) {
22560
+ // `createClient` always returns the provider's configured `HttpClient` implementation —
22561
+ // cast through `unknown` to hand back the caller-requested implementation type `T`.
22415
22562
  return this.createClient(key);
22416
22563
  }
22417
22564
  /**
@@ -22423,14 +22570,19 @@ class HttpClientProvider extends BaseModuleProvider {
22423
22570
  *
22424
22571
  * @param keyOrConfig - The key or configuration object for the HTTP client.
22425
22572
  * @returns The resolved HTTP client configuration.
22573
+ * @throws {ClientNotFoundException} When `keyOrConfig` is a string that is neither a registered
22574
+ * client key nor a URL-like value.
22426
22575
  */
22427
22576
  _resolveConfig(keyOrConfig) {
22577
+ // a string may reference a registered client key or an ad-hoc URL; anything else is used as-is
22428
22578
  if (typeof keyOrConfig === 'string') {
22429
22579
  const config = this.config.clients[keyOrConfig];
22580
+ // an absolute http(s) URL can be used directly as an ad-hoc baseUri
22430
22581
  if (!config && isURL(keyOrConfig)) {
22431
22582
  return { baseUri: keyOrConfig };
22432
22583
  }
22433
22584
  else if (!config && looksLikeURL(keyOrConfig)) {
22585
+ // recover from a missing protocol instead of failing outright, but warn so it can be fixed
22434
22586
  console.warn(`[HttpClientProvider] "${keyOrConfig}" looks like a URL but is missing the http:// or https:// protocol. ` +
22435
22587
  `Treating it as "https://${keyOrConfig}". ` +
22436
22588
  `Pass a fully-qualified URL to silence this warning.`);
@@ -22543,8 +22695,8 @@ const jsonSelector = async (response) => {
22543
22695
  * @throws {Error} If the response is not successful or if there is an error parsing the response.
22544
22696
  */
22545
22697
  const blobSelector = async (response) => {
22698
+ // treat any non-2xx response as a failure rather than attempting to read its body
22546
22699
  if (!response.ok) {
22547
- // Throw an error if the network response is not successful
22548
22700
  throw new Error('network response was not OK');
22549
22701
  }
22550
22702
  // Status code 204 indicates no content, so throw an error
@@ -22552,6 +22704,7 @@ const blobSelector = async (response) => {
22552
22704
  throw new Error('no content');
22553
22705
  }
22554
22706
  // Extract the filename from the 'content-disposition' header
22707
+ // locate the segment carrying the filename directive; other directives (e.g. inline/attachment) are ignored
22555
22708
  const filename = response.headers
22556
22709
  .get('content-disposition')
22557
22710
  ?.split(';')
@@ -22563,7 +22716,7 @@ const blobSelector = async (response) => {
22563
22716
  const blob = await response.blob();
22564
22717
  return { filename, blob };
22565
22718
  }
22566
- catch (err) {
22719
+ catch (_err) {
22567
22720
  // Throw an error if there's a problem parsing the response
22568
22721
  throw Error('failed to parse response');
22569
22722
  }
@@ -22599,16 +22752,23 @@ class HttpClient {
22599
22752
  _abort$ = new Subject();
22600
22753
  /**
22601
22754
  * A stream of requests that are about to be executed.
22755
+ * @returns An `Observable` that emits each request before it is executed.
22602
22756
  */
22603
22757
  get request$() {
22604
22758
  return this._request$.asObservable();
22605
22759
  }
22606
22760
  /**
22607
22761
  * A stream of responses that have been received.
22762
+ * @returns An `Observable` that emits each response as it is received.
22608
22763
  */
22609
22764
  get response$() {
22610
22765
  return this._response$.asObservable();
22611
22766
  }
22767
+ /**
22768
+ * Creates a new `HttpClient`.
22769
+ * @param uri - The base URI used to resolve relative request paths.
22770
+ * @param options - Optional request and response handlers.
22771
+ */
22612
22772
  constructor(uri, options) {
22613
22773
  this.uri = uri;
22614
22774
  this.requestHandler = new HttpRequestHandler(options?.requestHandler);
@@ -22627,6 +22787,7 @@ class HttpClient {
22627
22787
  /**
22628
22788
  * Fetches data from the specified path and returns a stream response.
22629
22789
  *
22790
+ * @template T - The expected shape of the fetched data.
22630
22791
  * @param path - The path to fetch data from.
22631
22792
  * @param args - Optional request initialization options, including a custom selector function.
22632
22793
  * @returns A stream response containing the fetched data.
@@ -22637,6 +22798,7 @@ class HttpClient {
22637
22798
  /**
22638
22799
  * Fetches data from the specified path and returns a Promise containing the fetched data.
22639
22800
  *
22801
+ * @template T - The expected shape of the fetched data.
22640
22802
  * @param path - The path to fetch data from.
22641
22803
  * @param args - Optional request initialization options, including a custom selector function.
22642
22804
  * @returns A Promise containing the fetched data.
@@ -22644,13 +22806,20 @@ class HttpClient {
22644
22806
  fetch(path, args) {
22645
22807
  return firstValueFrom(this.fetch$(path, args));
22646
22808
  }
22647
- /** @deprecated */
22809
+ /**
22810
+ * @deprecated Use {@link fetch} instead.
22811
+ * @template T - The expected shape of the fetched data.
22812
+ * @param path - The path to fetch data from.
22813
+ * @param args - Optional request initialization options, including a custom selector function.
22814
+ * @returns A Promise containing the fetched data.
22815
+ */
22648
22816
  fetchAsync(path, args) {
22649
22817
  return this.fetch(path, args);
22650
22818
  }
22651
22819
  /**
22652
22820
  * Fetches data from the specified path and returns a stream response containing the data in JSON format.
22653
22821
  *
22822
+ * @template T - The expected shape of the parsed JSON data.
22654
22823
  * @param path - The path to fetch the data from.
22655
22824
  * @param args - Optional request initialization options, including a custom selector function and request body.
22656
22825
  * - `body`: The request body, which will be automatically serialized to JSON if it's an object.
@@ -22674,6 +22843,7 @@ class HttpClient {
22674
22843
  /**
22675
22844
  * Fetches data from the specified path and returns a Promise containing the fetched data in JSON format.
22676
22845
  *
22846
+ * @template T - The expected shape of the parsed JSON data.
22677
22847
  * @param path - The path to fetch the data from.
22678
22848
  * @param args - Optional request initialization options, including a custom selector function and request body.
22679
22849
  * - `body`: The request body, which will be automatically serialized to JSON if it's an object.
@@ -22687,6 +22857,7 @@ class HttpClient {
22687
22857
  /**
22688
22858
  * Fetches a blob resource from the specified path and returns a stream response.
22689
22859
  *
22860
+ * @template T - The expected shape of the fetched blob data.
22690
22861
  * @param path - The path to the blob resource.
22691
22862
  * @param args - Optional request initialization options, including a custom selector function.
22692
22863
  * @returns A stream response containing the fetched blob data.
@@ -22705,6 +22876,7 @@ class HttpClient {
22705
22876
  /**
22706
22877
  * Fetches a blob from the specified path and returns a Promise that resolves to the blob result.
22707
22878
  *
22879
+ * @template T - The expected shape of the fetched blob data.
22708
22880
  * @param path - The path to fetch the blob from.
22709
22881
  * @param args - Optional arguments for the fetch request, including request body, headers, and response type.
22710
22882
  * @returns A Promise that resolves to the blob result.
@@ -22746,13 +22918,21 @@ class HttpClient {
22746
22918
  // Call the fetch$ method with the provided path and the constructed init object
22747
22919
  return this._fetch$(path, { selector, ...args, headers });
22748
22920
  }
22749
- /** @deprecated */
22921
+ /**
22922
+ * @deprecated Use {@link json} instead.
22923
+ * @template T - The expected shape of the parsed JSON data.
22924
+ * @param path - The path to fetch the data from.
22925
+ * @param args - Optional request initialization options, including a custom selector function and request body.
22926
+ * @returns A Promise containing the fetched data in JSON format.
22927
+ */
22750
22928
  jsonAsync(path, args) {
22751
22929
  return this.json(path, args);
22752
22930
  }
22753
22931
  /**
22754
22932
  * Executes an HTTP request using the specified method and path.
22755
22933
  *
22934
+ * @template T - The expected shape of the result.
22935
+ * @template TMethod - The name of the `IHttpClient` method to invoke.
22756
22936
  * @param method - The HTTP method to use for the request, such as 'fetch', 'json', or 'blob'.
22757
22937
  * @param path - The path to the resource to fetch.
22758
22938
  * @param init - Optional request initialization options, including request body, headers, and response type.
@@ -22772,10 +22952,13 @@ class HttpClient {
22772
22952
  /**
22773
22953
  * Fetches data from the specified path and returns an Observable that emits the response.
22774
22954
  *
22955
+ * @template T - The expected shape of the emitted data.
22775
22956
  * @param path - The path to fetch the data from.
22776
22957
  * @param args - Optional arguments for the fetch request, including a response selector function, request body, headers, and response type.
22777
22958
  * @returns {Observable<T>} An Observable that emits the response data.
22778
22959
  *
22960
+ * @throws {HttpResponseError} When the optional `selector` throws while transforming the response.
22961
+ *
22779
22962
  * This method handles the following steps:
22780
22963
  * 1. Resolves the full URL by combining the base URI and the provided path.
22781
22964
  * 2. Prepares the request by passing it through the `requestHandler.process()` method.
@@ -22786,6 +22969,9 @@ class HttpClient {
22786
22969
  */
22787
22970
  _fetch$(path, args) {
22788
22971
  const { selector, ...options } = args || {};
22972
+ // `fromFetch` yields the raw fetch `Response`, but `responseHandler.process()` (called via
22973
+ // `_prepareResponse`) expects the pipeline's generic `TResponse` shape — cast through
22974
+ // `unknown` since the two are only compatible after that processing step.
22789
22975
  const response$ = of({
22790
22976
  ...options,
22791
22977
  path,
@@ -22803,6 +22989,7 @@ class HttpClient {
22803
22989
  tap((x) => this._response$.next(x)),
22804
22990
  /** execute selector */
22805
22991
  switchMap((response) => {
22992
+ // only run the selector when one was provided; otherwise pass the response through untouched
22806
22993
  if (selector) {
22807
22994
  try {
22808
22995
  return selector(response);
@@ -22817,6 +23004,8 @@ class HttpClient {
22817
23004
  }),
22818
23005
  /** cancel request on abort signal */
22819
23006
  takeUntil(this._abort$));
23007
+ // The pipe above resolves to the per-call generic `T` (via the optional `selector`), but
23008
+ // the observable's static type tracks the class-level `TResponse` — cast to the caller's `T`.
22820
23009
  return response$;
22821
23010
  }
22822
23011
  /**
@@ -22886,6 +23075,7 @@ class HttpClientMsal extends HttpClient {
22886
23075
  *
22887
23076
  * @overrides HttpClient.fetch$
22888
23077
  *
23078
+ * @template T - The expected response type.
22889
23079
  * @param path - The path to the resource to fetch.
22890
23080
  * @param init - An optional `MsalFetchRequestInit` object that can include the `scopes` property.
22891
23081
  * @returns An `Observable` that emits the fetched resource.
@@ -22937,15 +23127,18 @@ const module$4 = {
22937
23127
  */
22938
23128
  initialize: async ({ config, hasModule, requireInstance, }) => {
22939
23129
  const httpProvider = new HttpClientProvider(config);
23130
+ // wire up an MSAL bearer-token handler only when the auth module is registered
22940
23131
  if (hasModule('auth')) {
22941
23132
  const authProvider = await requireInstance('auth');
22942
23133
  httpProvider.defaultHttpRequestHandler.set('MSAL', async (request) => {
22943
23134
  const { scopes = [] } = request;
23135
+ // only attempt to acquire a token when the request actually declares scopes
22944
23136
  if (scopes.length) {
22945
- /** TODO should be try catch, check caller for handling */
23137
+ /** TODO(#5143): should be try catch, check caller for handling */
22946
23138
  const accessToken = await authProvider.acquireAccessToken({
22947
23139
  request: { scopes },
22948
23140
  });
23141
+ // without a token there's nothing to attach, fall through to the default request
22949
23142
  if (accessToken) {
22950
23143
  const headers = new Headers(request.headers);
22951
23144
  headers.set('Authorization', `Bearer ${accessToken}`);
@@ -23471,7 +23664,7 @@ var objectTraps = {
23471
23664
  ) && isArrayIndex(prop)) {
23472
23665
  return value;
23473
23666
  }
23474
- if (value === peek(state.base_, prop)) {
23667
+ if (value === peek(state.base_, prop) || isRelocatedBaseRef(state, prop, value)) {
23475
23668
  prepareCopy(state);
23476
23669
  const childKey = state.type_ === 1 /* Array */ ? +prop : prop;
23477
23670
  const childDraft = createProxy(state.scope_, value, state, childKey);
@@ -23505,7 +23698,7 @@ var objectTraps = {
23505
23698
  markChanged(state);
23506
23699
  }
23507
23700
  if (state.copy_[prop] === value && // special case: handle new props with value 'undefined'
23508
- (value !== void 0 || prop in state.copy_) || // special case: NaN
23701
+ (value !== void 0 || has(state.copy_, prop, state.type_)) || // special case: NaN
23509
23702
  Number.isNaN(value) && Number.isNaN(state.copy_[prop]))
23510
23703
  return true;
23511
23704
  state.copy_[prop] = value;
@@ -23574,6 +23767,12 @@ function peek(draft, prop) {
23574
23767
  const source = state ? latest(state) : draft;
23575
23768
  return source[prop];
23576
23769
  }
23770
+ function isRelocatedBaseRef(state, prop, value) {
23771
+ if (state.type_ !== 1 /* Array */ || !state.allIndicesReassigned_ || state.assigned_?.get(prop) || !isDraftable(value) || value[DRAFT_STATE]) {
23772
+ return false;
23773
+ }
23774
+ return state.baseRefs_.has(value);
23775
+ }
23577
23776
  function readPropFromProto(state, source, prop) {
23578
23777
  const desc = getDescriptorFromProto(source, prop);
23579
23778
  return desc ? VALUE in desc ? desc[VALUE] : (
@@ -23899,18 +24098,21 @@ class FlowSubject extends Observable {
23899
24098
  #state;
23900
24099
  /**
23901
24100
  * Observable stream of actions dispatched to the subject.
24101
+ * @returns An observable of dispatched actions.
23902
24102
  */
23903
24103
  get action$() {
23904
24104
  return this.#action.asObservable();
23905
24105
  }
23906
24106
  /**
23907
24107
  * The current value of state.
24108
+ * @returns The current state value.
23908
24109
  */
23909
24110
  get value() {
23910
24111
  return this.#state.value;
23911
24112
  }
23912
24113
  /**
23913
24114
  * Flag to indicate if the observable is closed.
24115
+ * @returns `true` if both the state and action subjects are closed.
23914
24116
  */
23915
24117
  get closed() {
23916
24118
  return this.#state.closed || this.#action.closed;
@@ -23927,6 +24129,7 @@ class FlowSubject extends Observable {
23927
24129
  });
23928
24130
  const initial = 'getInitialState' in reducer ? reducer.getInitialState() : initialState;
23929
24131
  this.#state = new BehaviorSubject(initial);
24132
+ // Reduce dispatched actions into state, skipping emissions that produce an unchanged value
23930
24133
  this.#action.pipe(scan(reducer, initial), distinctUntilChanged()).subscribe(this.#state);
23931
24134
  this.reset = () => this.#state.next(initial);
23932
24135
  }
@@ -23945,27 +24148,32 @@ class FlowSubject extends Observable {
23945
24148
  /**
23946
24149
  * Selects a derived state from the observable state and emits only when the selected state changes.
23947
24150
  *
24151
+ * @template T - The derived state type returned by the selector.
23948
24152
  * @param selector - A function that takes the current state and returns a derived state.
23949
24153
  * @param comparator - An optional function that compares the previous and current derived states to determine if a change has occurred.
23950
24154
  * @returns An observable that emits the derived state whenever it changes.
23951
24155
  */
23952
24156
  select(selector, comparator) {
24157
+ // Derive and de-duplicate the selected value from the state stream
23953
24158
  return this.#state.pipe(map$1(selector), distinctUntilChanged(comparator));
23954
24159
  }
23955
24160
  /**
23956
24161
  * Adds an effect that listens for actions and performs side effects.
23957
24162
  *
24163
+ * @template TType - The action type(s) to listen for.
23958
24164
  * @param actionTypeOrFn The type of action to listen for, an array of action types, or the effect function itself.
23959
24165
  * @param fn The effect function to execute when the action is dispatched, if the first parameter is an action type.
23960
24166
  * @returns A subscription to the effect.
23961
24167
  */
23962
24168
  addEffect(actionTypeOrFn, fn) {
24169
+ // Narrow the action stream to the requested action type(s), or use it as-is when a plain effect function was given
23963
24170
  const action$ = typeof actionTypeOrFn === 'function'
23964
24171
  ? this.action$
23965
24172
  : Array.isArray(actionTypeOrFn)
23966
24173
  ? this.action$.pipe(filterAction(...actionTypeOrFn))
23967
24174
  : this.action$.pipe(filterAction(actionTypeOrFn));
23968
24175
  const mapper = (fn ? fn : actionTypeOrFn);
24176
+ // Run the effect for each matching action, then filter and reschedule any resulting action
23969
24177
  return action$
23970
24178
  .pipe(mergeMap((action) => from(new Promise((resolve, reject) => {
23971
24179
  try {
@@ -23974,7 +24182,9 @@ class FlowSubject extends Observable {
23974
24182
  catch (err) {
23975
24183
  reject(err);
23976
24184
  }
23977
- })).pipe(catchError((err) => {
24185
+ }))
24186
+ // Swallow effect errors so a single failing effect doesn't tear down the subscription
24187
+ .pipe(catchError((err) => {
23978
24188
  console.warn('unhandled effect', err);
23979
24189
  return EMPTY;
23980
24190
  }))), filter((x) => !!x), observeOn(asyncScheduler))
@@ -24016,9 +24226,11 @@ class FlowSubject extends Observable {
24016
24226
  */
24017
24227
  addFlow(fn) {
24018
24228
  const epic$ = fn(this.action$, this);
24229
+ // Fail fast if the flow function forgot to return an observable
24019
24230
  if (!epic$) {
24020
24231
  throw new TypeError(`addEpic: one of the provided effects "${fn.name || '<anonymous>'}" does not return a stream. Double check you're not missing a return statement!`);
24021
24232
  }
24233
+ // Log and swallow flow errors so a single failing flow doesn't tear down the subject
24022
24234
  return epic$
24023
24235
  .pipe(catchError((err) => {
24024
24236
  console.trace('unhandled exception, epic closed!', err);
@@ -24054,18 +24266,38 @@ class FlowSubject extends Observable {
24054
24266
  }
24055
24267
  }
24056
24268
 
24057
- /* eslint-disable @typescript-eslint/no-explicit-any */
24269
+ /**
24270
+ * Internal helper utilities for constructing and inspecting Redux-style
24271
+ * lifecycle action types (`::request`, `::success`, `::failure`).
24272
+ */
24273
+ /**
24274
+ * Separates an action's base type from its lifecycle suffix.
24275
+ */
24276
+ const actionSuffixDivider = '::';
24277
+
24278
+ // biome-ignore-all lint/suspicious/noExplicitAny: ported redux-toolkit conditional-type dispatch logic (e.g. IsAny<T, ...>) requires real `any`, not `unknown`, to distinguish the two at the type level
24279
+ // biome-ignore-all lint/suspicious/noConfusingVoidType: `void` here is the ported redux-toolkit sentinel meaning "no payload type provided", distinct from `undefined` (see IfVoid in ts-helpers.ts) — replacing with `undefined` would change dispatch semantics
24058
24280
  /**
24059
24281
  * taken from https://github.com/reduxjs/redux-toolkit/tree/master/packages/toolkit/src
24060
24282
  */
24061
- // eslint-disable-next-line @typescript-eslint/ban-types
24283
+ /**
24284
+ * Creates the runtime action creator implementation shared by the overloads above.
24285
+ *
24286
+ * @param type - The action type to assign to created actions.
24287
+ * @param prepareAction - An optional function that prepares payload, metadata, and errors.
24288
+ * @returns A callable action creator with type and matching metadata.
24289
+ * @throws {Error} When the prepare callback does not return an action object.
24290
+ */
24062
24291
  function createAction(type, prepareAction) {
24063
24292
  function actionCreator(...args) {
24293
+ // Use the prepare callback when the caller needs custom payload construction.
24064
24294
  if (prepareAction) {
24065
24295
  const prepared = prepareAction(...args);
24296
+ // Reject invalid prepare callbacks before reading their action fields.
24066
24297
  if (!prepared) {
24067
24298
  throw new Error('prepareAction did not return an object');
24068
24299
  }
24300
+ // Merge optional metadata and error fields into the prepared action.
24069
24301
  return {
24070
24302
  type,
24071
24303
  payload: prepared.payload,
@@ -24080,35 +24312,50 @@ function createAction(type, prepareAction) {
24080
24312
  actionCreator.match = (action) => action.type === type;
24081
24313
  return actionCreator;
24082
24314
  }
24083
- const actionSuffixDivider = '::';
24084
24315
 
24085
- /* eslint-disable @typescript-eslint/no-explicit-any */
24316
+ // biome-ignore-all lint/suspicious/noExplicitAny: generic constraints mirror create-action.ts's ported redux-toolkit PrepareAction<any> dispatch pattern
24317
+ /**
24318
+ * Creates the runtime async action implementation shared by the overloads above.
24319
+ *
24320
+ * @param type - The base action type string.
24321
+ * @param request - A prepare function for the request action payload.
24322
+ * @param success - A prepare function for the success action payload.
24323
+ * @param failure - An optional prepare function for the failure action payload.
24324
+ * @returns An action creator with request, success, and optional failure actions.
24325
+ */
24086
24326
  function createAsyncAction(type, request, success, failure) {
24087
24327
  const action = createAction([type, 'request'].join(actionSuffixDivider), request);
24328
+ // Attach the success action only when the caller supplied its prepare function.
24088
24329
  if (success) {
24330
+ // Add the success action while preserving the request creator's identity.
24089
24331
  Object.assign(action, {
24090
24332
  success: createAction([type, 'success'].join(actionSuffixDivider), success),
24091
24333
  });
24092
24334
  }
24335
+ // Attach the failure action only for async workflows that support failure results.
24093
24336
  if (failure) {
24337
+ // Add the failure action alongside the existing request and success actions.
24094
24338
  Object.assign(action, {
24095
24339
  failure: createAction([type, 'failure'].join(actionSuffixDivider), failure),
24096
24340
  });
24097
24341
  }
24342
+ // `action` is progressively mutated with `success`/`failure` properties above, so its static
24343
+ // type can't reflect the final shape declared by this function's overload signatures.
24098
24344
  return action;
24099
24345
  }
24100
24346
 
24101
24347
  function freezeDraftable(val) {
24102
- // biome-ignore lint/suspicious/noEmptyBlockStatements: This is a valid use case for an empty block statement
24103
24348
  return isDraftable(val) ? produce(val, () => { }) : val;
24104
24349
  }
24105
24350
  function isStateFunction(x) {
24106
24351
  return typeof x === 'function';
24107
24352
  }
24353
+ /** @inheritdoc */
24108
24354
  function createReducer$2(initialState, mapOrBuilderCallback) {
24109
24355
  const [actionsMap, finalActionMatchers, finalDefaultCaseReducer] = executeReducerBuilderCallback(mapOrBuilderCallback);
24110
24356
  // Ensure the initial state gets frozen either way (if draftable)
24111
24357
  let getInitialState;
24358
+ // A lazy initializer must be invoked before its result can be frozen
24112
24359
  if (isStateFunction(initialState)) {
24113
24360
  getInitialState = () => freezeDraftable(initialState());
24114
24361
  }
@@ -24119,19 +24366,31 @@ function createReducer$2(initialState, mapOrBuilderCallback) {
24119
24366
  function reducer(state, action) {
24120
24367
  let caseReducers = [
24121
24368
  actionsMap[action.type],
24122
- ...finalActionMatchers.filter(({ matcher }) => matcher(action)).map(({ reducer }) => reducer),
24369
+ ...finalActionMatchers
24370
+ // Keep only matchers whose predicate matches this action
24371
+ .filter(({ matcher }) => matcher(action))
24372
+ // Extract just the matched reducers
24373
+ .map(({ reducer }) => reducer),
24123
24374
  ];
24124
- if (finalDefaultCaseReducer && caseReducers.filter((cr) => !!cr).length === 0) {
24375
+ // Fall back to the default case reducer when nothing else matched this action
24376
+ // Check whether any slot actually holds a reducer
24377
+ const hasMatchedReducer = caseReducers.filter((cr) => !!cr).length > 0;
24378
+ // Only substitute the default case when no case/matcher reducer actually matched
24379
+ if (finalDefaultCaseReducer && !hasMatchedReducer) {
24125
24380
  caseReducers = [finalDefaultCaseReducer];
24126
24381
  }
24382
+ // Run each matched case reducer against the previous state, producing the next state
24127
24383
  return caseReducers.reduce((previousState, caseReducer) => {
24384
+ // Only transform state when a case reducer was matched for this slot
24128
24385
  if (caseReducer) {
24386
+ // Reuse an existing draft when already inside a `createNextState` call
24129
24387
  if (isDraft(previousState)) {
24130
24388
  // If it's already a draft, we must already be inside a `createNextState` call,
24131
24389
  // likely because this is being wrapped in `createReducer`, `createSlice`, or nested
24132
24390
  // inside an existing draft. It's safe to just pass the draft to the mutator.
24133
24391
  const draft = previousState; // We can assume this is already a draft
24134
24392
  const result = caseReducer(draft, action);
24393
+ // Treat an undefined result as "no change" rather than clearing the state
24135
24394
  if (result === undefined) {
24136
24395
  return previousState;
24137
24396
  }
@@ -24141,7 +24400,9 @@ function createReducer$2(initialState, mapOrBuilderCallback) {
24141
24400
  // If state is not draftable (ex: a primitive, such as 0), we want to directly
24142
24401
  // return the caseReducer func and not wrap it with produce.
24143
24402
  const result = caseReducer(previousState, action);
24403
+ // Treat an undefined result as "no change", unless there's no previous state to fall back to
24144
24404
  if (result === undefined) {
24405
+ // A `null` previous state has nothing to fall back to, so undefined is legitimately "no change"
24145
24406
  if (previousState === null) {
24146
24407
  return previousState;
24147
24408
  }
@@ -24151,7 +24412,7 @@ function createReducer$2(initialState, mapOrBuilderCallback) {
24151
24412
  }
24152
24413
  else {
24153
24414
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
24154
- // @ts-ignore
24415
+ // @ts-expect-error
24155
24416
  // createNextState() produces an Immutable<Draft<S>> rather
24156
24417
  // than an Immutable<S>, and TypeScript cannot find out how to reconcile
24157
24418
  // these two types.
@@ -24166,12 +24427,22 @@ function createReducer$2(initialState, mapOrBuilderCallback) {
24166
24427
  reducer.getInitialState = getInitialState;
24167
24428
  return reducer;
24168
24429
  }
24430
+ /**
24431
+ * Executes a reducer builder callback against a fresh {@link ActionReducerMapBuilder},
24432
+ * collecting the case reducers, matchers, and default case it registered.
24433
+ *
24434
+ * @template TState - The reducer's state type.
24435
+ * @template TAction - The union of actions the reducer handles.
24436
+ * @param builderCallback - The callback passed to {@link createReducer} that registers cases via the builder.
24437
+ * @returns A tuple of the action-to-reducer map, matcher list, and optional default case reducer.
24438
+ */
24169
24439
  function executeReducerBuilderCallback(builderCallback) {
24170
24440
  const actionsMap = {};
24171
24441
  const actionMatchers = [];
24172
24442
  let defaultCaseReducer;
24173
24443
  const builder = {
24174
24444
  addCase(typeOrActionCreator, reducer) {
24445
+ // Skip these dev-only ordering checks in production builds to avoid the extra overhead
24175
24446
  if (process.env.NODE_ENV !== 'production') {
24176
24447
  /*
24177
24448
  * to keep the definition by the user in line with actual behavior,
@@ -24181,11 +24452,13 @@ function executeReducerBuilderCallback(builderCallback) {
24181
24452
  if (actionMatchers.length > 0) {
24182
24453
  throw new Error('`builder.addCase` should only be called before calling `builder.addMatcher`');
24183
24454
  }
24455
+ // A default case must always be registered last, after every specific case
24184
24456
  if (defaultCaseReducer) {
24185
24457
  throw new Error('`builder.addCase` should only be called before calling `builder.addDefaultCase`');
24186
24458
  }
24187
24459
  }
24188
24460
  const type = typeof typeOrActionCreator === 'string' ? typeOrActionCreator : typeOrActionCreator.type;
24461
+ // Each action type may only be handled by a single case reducer
24189
24462
  if (type in actionsMap) {
24190
24463
  throw new Error('addCase cannot be called with two reducers for the same action type');
24191
24464
  }
@@ -24193,7 +24466,9 @@ function executeReducerBuilderCallback(builderCallback) {
24193
24466
  return builder;
24194
24467
  },
24195
24468
  addMatcher(matcher, reducer) {
24469
+ // Skip this dev-only ordering check in production builds to avoid the extra overhead
24196
24470
  if (process.env.NODE_ENV !== 'production') {
24471
+ // A default case must always be registered last, after every matcher
24197
24472
  if (defaultCaseReducer) {
24198
24473
  throw new Error('`builder.addMatcher` should only be called before calling `builder.addDefaultCase`');
24199
24474
  }
@@ -24202,7 +24477,9 @@ function executeReducerBuilderCallback(builderCallback) {
24202
24477
  return builder;
24203
24478
  },
24204
24479
  addDefaultCase(reducer) {
24480
+ // Skip this dev-only uniqueness check in production builds to avoid the extra overhead
24205
24481
  if (process.env.NODE_ENV !== 'production') {
24482
+ // Only one default case reducer may be registered per builder
24206
24483
  if (defaultCaseReducer) {
24207
24484
  throw new Error('`builder.addDefaultCase` can only be called once');
24208
24485
  }
@@ -24219,8 +24496,10 @@ function executeReducerBuilderCallback(builderCallback) {
24219
24496
  ];
24220
24497
  }
24221
24498
 
24499
+ /** @inheritdoc */
24500
+ function isObservableInput(
24222
24501
  // biome-ignore lint/suspicious/noExplicitAny: Should allow any input type
24223
- function isObservableInput(input) {
24502
+ input) {
24224
24503
  // Check for null or undefined
24225
24504
  if (input === null || input === undefined) {
24226
24505
  return false;
@@ -24290,7 +24569,6 @@ function isObservableInput(input) {
24290
24569
  * // Primitive
24291
24570
  * toObservable(42).subscribe(x => console.log(x)); // 42
24292
24571
  */
24293
- // biome-ignore lint/suspicious/noExplicitAny: must be any to allow any type of input
24294
24572
  function toObservable(input, ...args) {
24295
24573
  // If input is already an ObservableInput (Observable, Promise, Iterable, etc), use RxJS 'from' to convert it
24296
24574
  if (isObservableInput(input)) {
@@ -24468,6 +24746,7 @@ var deepmerge = /*@__PURE__*/getDefaultExportFromCjs(deepmergeExports);
24468
24746
  * @returns The merged `MetaData` object, or `undefined` if both inputs are undefined.
24469
24747
  */
24470
24748
  const mergeMetadata = (source, target) => {
24749
+ // Nothing to merge when neither side has metadata
24471
24750
  if (!source && !target) {
24472
24751
  return undefined;
24473
24752
  }
@@ -24490,15 +24769,21 @@ const mergeMetadata = (source, target) => {
24490
24769
  *
24491
24770
  * @internal
24492
24771
  */
24772
+ // Deliberately co-located with `mergeMetadata` above
24773
+ // fusion-lint-disable-next-line single-export-per-file
24493
24774
  const mergeTelemetryItem = (target, source) => {
24775
+ // Both arguments are required to produce a valid merged telemetry item
24494
24776
  if (!target || !source) {
24495
24777
  throw new Error('Both target and source must be defined for merging telemetry items.');
24496
24778
  }
24779
+ // A type mismatch would produce an inconsistent telemetry item, so fail fast
24497
24780
  if (target.type && source.type && target.type !== source.type) {
24498
24781
  throw new Error('Mismatched telemetry item types.');
24499
24782
  }
24783
+ // Combine both scopes and de-duplicate via a Set before flattening back to an array
24500
24784
  const scope = [...new Set([...(target.scope ?? []), ...(source.scope ?? [])])];
24501
24785
  const metadata = mergeMetadata(target.metadata, source.metadata);
24786
+ // Source properties take precedence over target, with scope/metadata merged separately above
24502
24787
  return { ...target, ...source, scope, metadata };
24503
24788
  };
24504
24789
 
@@ -24529,11 +24814,18 @@ const mergeTelemetryItem = (target, source) => {
24529
24814
  class TelemetryConfigurator extends BaseConfigBuilder {
24530
24815
  #adaptersCallbacks = {};
24531
24816
  #metadata = [];
24817
+ /**
24818
+ * Creates a new `TelemetryConfigurator` and wires up the async adapter
24819
+ * resolution and metadata merging pipelines used by `createConfigAsync`.
24820
+ */
24532
24821
  constructor() {
24533
24822
  super();
24534
24823
  // Configure async adapter resolution using mergeMap to handle Promise/Observable adapter factories
24535
24824
  this._set('adapters', (args) => {
24536
- return from(Object.entries(this.#adaptersCallbacks)).pipe(mergeMap(([identifier, adapterFn]) => from(adapterFn(args)).pipe(filter((adapter) => !!adapter), map$1((adapter) => [identifier, adapter]))), scan((acc, [identifier, adapter]) => {
24825
+ // Resolve every adapter factory concurrently and merge them into a single record
24826
+ return from(Object.entries(this.#adaptersCallbacks)).pipe(mergeMap(([identifier, adapterFn]) =>
24827
+ // Drop adapters whose factory resolved to a falsy value
24828
+ from(adapterFn(args)).pipe(filter((adapter) => !!adapter), map$1((adapter) => [identifier, adapter]))), scan((acc, [identifier, adapter]) => {
24537
24829
  acc[identifier] = adapter;
24538
24830
  return acc;
24539
24831
  }, {}), defaultIfEmpty({}), shareReplay({ bufferSize: 1, refCount: true }));
@@ -24541,12 +24833,15 @@ class TelemetryConfigurator extends BaseConfigBuilder {
24541
24833
  // Configure metadata merging - handles multiple sync/async metadata sources
24542
24834
  this._set('metadata', async () => {
24543
24835
  const metadataItems = this.#metadata;
24544
- return (...args) => from(metadataItems).pipe(concatMap((metadata) => toObservable(metadata, ...args)), scan((acc, current) => mergeMetadata(acc, current) ?? {}, {}), last(), shareReplay({ bufferSize: 1, refCount: true }));
24836
+ return (...args) =>
24837
+ // Merge every registered metadata source in order, emitting the final accumulated result
24838
+ from(metadataItems).pipe(concatMap((metadata) => toObservable(metadata, ...args)), scan((acc, current) => mergeMetadata(acc, current) ?? {}, {}), last(), shareReplay({ bufferSize: 1, refCount: true }));
24545
24839
  });
24546
24840
  }
24547
24841
  /**
24548
24842
  * Registers a telemetry adapter with the configurator.
24549
24843
  *
24844
+ * @param identifier - The key used to register and later resolve this adapter.
24550
24845
  * @param adapter - The telemetry adapter to be added. The adapter's identifier is used as the key.
24551
24846
  * @returns The current instance of the configurator for method chaining.
24552
24847
  */
@@ -24556,7 +24851,8 @@ class TelemetryConfigurator extends BaseConfigBuilder {
24556
24851
  /**
24557
24852
  * Configures a telemetry adapter with the configurator.
24558
24853
  *
24559
- * @param adapter - A callback function that returns a telemetry adapter instance
24854
+ * @param identifier - The key used to register and later resolve this adapter.
24855
+ * @param adapterFn - A callback function that returns a telemetry adapter instance
24560
24856
  * @returns The current instance for method chaining
24561
24857
  */
24562
24858
  configureAdapter(identifier, adapterFn) {
@@ -24621,7 +24917,7 @@ class TelemetryConfigurator extends BaseConfigBuilder {
24621
24917
  }
24622
24918
 
24623
24919
  // Generated by genversion.
24624
- const version$5 = '7.0.0';
24920
+ const version$5 = '7.0.1';
24625
24921
 
24626
24922
  /**
24627
24923
  * Enum representing the severity levels of telemetry items.
@@ -24715,6 +25011,8 @@ const TelemetryItemSchema = object({
24715
25011
  *
24716
25012
  * Extends TelemetryItemSchema and sets type to 'event'.
24717
25013
  */
25014
+ // Deliberately co-located with the other telemetry item schemas below
25015
+ // fusion-lint-disable-next-line single-export-per-file
24718
25016
  const TelemetryEventSchema = TelemetryItemSchema.extend({
24719
25017
  type: literal(TelemetryType.Event),
24720
25018
  });
@@ -24724,6 +25022,8 @@ const TelemetryEventSchema = TelemetryItemSchema.extend({
24724
25022
  * Extends TelemetryItemSchema and sets type to 'exception'.
24725
25023
  * Adds an exception property of type Error.
24726
25024
  */
25025
+ // Deliberately co-located with the other telemetry item schemas above/below
25026
+ // fusion-lint-disable-next-line single-export-per-file
24727
25027
  const TelemetryExceptionSchema = TelemetryItemSchema.extend({
24728
25028
  type: literal(TelemetryType.Exception),
24729
25029
  exception: _instanceof(Error).describe('The exception object.'),
@@ -24734,6 +25034,8 @@ const TelemetryExceptionSchema = TelemetryItemSchema.extend({
24734
25034
  * Extends TelemetryItemSchema and sets type to 'metric'.
24735
25035
  * Adds a value property for the metric value.
24736
25036
  */
25037
+ // Deliberately co-located with the other telemetry item schemas above/below
25038
+ // fusion-lint-disable-next-line single-export-per-file
24737
25039
  const TelemetryMetricSchema = TelemetryItemSchema.extend({
24738
25040
  type: literal(TelemetryType.Metric),
24739
25041
  value: number$1().describe('The value of the metric.'),
@@ -24743,6 +25045,8 @@ const TelemetryMetricSchema = TelemetryItemSchema.extend({
24743
25045
  *
24744
25046
  * Extends TelemetryEventSchema and allows passthrough of additional properties.
24745
25047
  */
25048
+ // Deliberately co-located with the other telemetry item schemas above
25049
+ // fusion-lint-disable-next-line single-export-per-file
24746
25050
  const TelemetryCustomEventSchema = TelemetryEventSchema.extend({
24747
25051
  type: literal(TelemetryType.Custom),
24748
25052
  }).passthrough();
@@ -24764,7 +25068,6 @@ const TelemetryCustomEventSchema = TelemetryEventSchema.extend({
24764
25068
  * @template TInit The type of the event details.
24765
25069
  * @template TType The type of the event type.
24766
25070
  */
24767
- // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: no other way to define a class with multiple signatures
24768
25071
  class FrameworkEvent {
24769
25072
  __type;
24770
25073
  #detail;
@@ -24843,6 +25146,7 @@ class FrameworkEvent {
24843
25146
  * If the event is cancelable, this method sets the `canceled` property to `true`.
24844
25147
  */
24845
25148
  preventDefault() {
25149
+ // Only cancelable events can be canceled
24846
25150
  if (this.cancelable) {
24847
25151
  this.#canceled = true;
24848
25152
  }
@@ -24857,44 +25161,54 @@ class FrameworkEvent {
24857
25161
  }
24858
25162
 
24859
25163
  /**
24860
- * Represents a telemetry event within the framework.
25164
+ * Event representing an error that occurred within a telemetry provider.
24861
25165
  *
24862
- * This event encapsulates a `TelemetryItem` and is associated with a specific `ITelemetryProvider` source.
24863
- * It extends the `FrameworkEvent` class, providing a standardized way to emit and handle telemetry-related events.
25166
+ * @remarks
25167
+ * This event is emitted when an error is encountered by an {@link ITelemetryProvider}.
25168
+ * It encapsulates the error details and the source provider.
24864
25169
  *
24865
25170
  * @example
24866
25171
  * ```typescript
24867
- * const event = new TelemetryEvent(item, provider);
25172
+ * const errorEvent = new TelemetryErrorEvent(new Error('Something went wrong'), telemetryProvider);
24868
25173
  * ```
24869
25174
  *
24870
- * @param item - The telemetry item containing event data.
24871
- * @param source - The telemetry provider that is the source of this event.
25175
+ * @extends FrameworkEvent
24872
25176
  */
24873
- class TelemetryEvent extends FrameworkEvent {
24874
- constructor(item, source) {
24875
- super('onTelemetry', { detail: { item }, source });
25177
+ class TelemetryErrorEvent extends FrameworkEvent {
25178
+ /**
25179
+ * Creates a new `TelemetryErrorEvent`.
25180
+ *
25181
+ * @param error - The error instance that was thrown.
25182
+ * @param source - The telemetry provider where the error originated.
25183
+ */
25184
+ constructor(error, source) {
25185
+ super('onTelemetryError', { detail: { error }, source });
24876
25186
  }
24877
25187
  }
25188
+
24878
25189
  /**
24879
- * Event representing an error that occurred within a telemetry provider.
25190
+ * Represents a telemetry event within the framework.
24880
25191
  *
24881
- * @remarks
24882
- * This event is emitted when an error is encountered by an {@link ITelemetryProvider}.
24883
- * It encapsulates the error details and the source provider.
25192
+ * This event encapsulates a `TelemetryItem` and is associated with a specific `ITelemetryProvider` source.
25193
+ * It extends the `FrameworkEvent` class, providing a standardized way to emit and handle telemetry-related events.
24884
25194
  *
24885
25195
  * @example
24886
25196
  * ```typescript
24887
- * const errorEvent = new TelemetryErrorEvent(new Error('Something went wrong'), telemetryProvider);
25197
+ * const event = new TelemetryEvent(item, provider);
24888
25198
  * ```
24889
25199
  *
24890
- * @extends FrameworkEvent
24891
- *
24892
- * @param error - The error instance that was thrown.
24893
- * @param source - The telemetry provider where the error originated.
25200
+ * @param item - The telemetry item containing event data.
25201
+ * @param source - The telemetry provider that is the source of this event.
24894
25202
  */
24895
- class TelemetryErrorEvent extends FrameworkEvent {
24896
- constructor(error, source) {
24897
- super('onTelemetryError', { detail: { error }, source });
25203
+ class TelemetryEvent extends FrameworkEvent {
25204
+ /**
25205
+ * Creates a new `TelemetryEvent`.
25206
+ *
25207
+ * @param item - The telemetry item containing event data.
25208
+ * @param source - The telemetry provider that is the source of this event.
25209
+ */
25210
+ constructor(item, source) {
25211
+ super('onTelemetry', { detail: { item }, source });
24898
25212
  }
24899
25213
  }
24900
25214
 
@@ -24942,6 +25256,7 @@ class Measurement {
24942
25256
  */
24943
25257
  clone(resetOptions = {}) {
24944
25258
  const clonedMeasurement = new Measurement(this.#provider, this.#data);
25259
+ // Preserve the original start time when requested, otherwise the clone starts fresh
24945
25260
  if (resetOptions.preserveStartTime) {
24946
25261
  clonedMeasurement.#startTime = this.#startTime;
24947
25262
  }
@@ -24956,6 +25271,7 @@ class Measurement {
24956
25271
  * Resets the measured flag and, unless `preserveStartTime` is true, updates the start time to the current performance time.
24957
25272
  */
24958
25273
  reset(options) {
25274
+ // Keep the existing start time when preserving, otherwise restart the clock
24959
25275
  if (!options?.preserveStartTime) {
24960
25276
  this.#startTime = performance.now();
24961
25277
  }
@@ -24970,6 +25286,7 @@ class Measurement {
24970
25286
  * @param options - Optional measurement options.
24971
25287
  * @param options.markAsMeasured - If true, marks this measurement as completed.
24972
25288
  * @param options.resetStartTime - If true, resets the start time after measuring.
25289
+ * @returns The elapsed duration in milliseconds since the last start time.
24973
25290
  */
24974
25291
  measure(data, options) {
24975
25292
  const measuredData = mergeTelemetryItem(this.#data, data ?? {});
@@ -24978,9 +25295,11 @@ class Measurement {
24978
25295
  ...measuredData,
24979
25296
  value: duration,
24980
25297
  });
25298
+ // Mark as measured only when explicitly requested by the caller
24981
25299
  if (options?.markAsMeasured) {
24982
25300
  this.#measured = true;
24983
25301
  }
25302
+ // Restart the clock only when explicitly requested by the caller
24984
25303
  if (options?.resetStartTime) {
24985
25304
  this.#startTime = performance.now();
24986
25305
  }
@@ -24989,7 +25308,7 @@ class Measurement {
24989
25308
  /**
24990
25309
  * Resolves a given promise, measures the result using the provided options, and returns the resolved value.
24991
25310
  *
24992
- * @typeParam T - The type of the resolved value from the promise.
25311
+ * @template T - The type of the resolved value from the promise.
24993
25312
  * @param promise - The promise to resolve.
24994
25313
  * @param options - Optional configuration for resolving and measuring:
24995
25314
  * - `data`: A value or a function that receives the resolved result and returns measurement data.
@@ -25007,7 +25326,7 @@ class Measurement {
25007
25326
  * The function `fn` can return either a value of type `T` or a Promise of type `T`.
25008
25327
  * Optionally, resolution options can be provided.
25009
25328
  *
25010
- * @typeParam T - The return type of the function to execute.
25329
+ * @template T - The return type of the function to execute.
25011
25330
  * @param fn - A function that returns a value or a Promise to be resolved.
25012
25331
  * @param options - Optional resolution options to customize the resolve behavior.
25013
25332
  * @returns A Promise that resolves to the result of the executed function.
@@ -25025,6 +25344,7 @@ class Measurement {
25025
25344
  * @see https://github.com/tc39/proposal-explicit-resource-management
25026
25345
  */
25027
25346
  [Symbol.dispose]() {
25347
+ // Ensure a measurement is always taken before the instance is discarded
25028
25348
  if (!this.#measured) {
25029
25349
  try {
25030
25350
  this.measure();
@@ -25067,7 +25387,10 @@ const resolveMetadata = (metadata, args) => {
25067
25387
  * @param args - The arguments required by the metadata extractor, including the telemetry item.
25068
25388
  * @returns An Observable emitting the telemetry item with applied metadata, or an error telemetry item if resolution fails.
25069
25389
  */
25390
+ // Deliberately co-located with `resolveMetadata` above
25391
+ // fusion-lint-disable-next-line single-export-per-file
25070
25392
  const applyMetadata = (metadata, args) => {
25393
+ // Resolve metadata, merge it into the item, and fall back to an error item on failure
25071
25394
  return resolveMetadata(metadata, args).pipe(defaultIfEmpty({}), // Ensure we always have an object to merge with
25072
25395
  first(), // Take the first emitted value from the metadata extractor
25073
25396
  // Merge the resolved metadata with the telemetry item.
@@ -25121,20 +25444,39 @@ class TelemetryProvider extends BaseModuleProvider {
25121
25444
  #filters;
25122
25445
  #defaultScope;
25123
25446
  #eventProvider;
25447
+ /**
25448
+ * Stream of every telemetry item tracked by this provider.
25449
+ *
25450
+ * @returns An observable emitting each tracked `TelemetryItem`.
25451
+ */
25124
25452
  get items() {
25125
25453
  return this.#items.asObservable();
25126
25454
  }
25127
25455
  /**
25128
25456
  * Returns true if the provider has been initialized.
25457
+ *
25458
+ * @returns `true` when {@link initialize} has completed, otherwise `false`.
25129
25459
  */
25130
25460
  get initialized() {
25131
25461
  return this.#initialized;
25132
25462
  }
25133
25463
  #metadata;
25134
25464
  #modules;
25465
+ /**
25466
+ * Sets the resolved framework module instances used to resolve metadata.
25467
+ *
25468
+ * @param value - The resolved framework module instances.
25469
+ */
25135
25470
  set modules(value) {
25136
25471
  this.#modules = value;
25137
25472
  }
25473
+ /**
25474
+ * Creates a new `TelemetryProvider`.
25475
+ *
25476
+ * @param config - Resolved telemetry configuration (adapters, metadata, scope, filters).
25477
+ * @param deps - Optional dependencies; supply an event module provider to dispatch
25478
+ * telemetry items as events.
25479
+ */
25138
25480
  constructor(config, deps) {
25139
25481
  super({ version: version$5, config });
25140
25482
  this.#adapters = config?.adapters ?? {};
@@ -25194,11 +25536,13 @@ class TelemetryProvider extends BaseModuleProvider {
25194
25536
  async _initializeAdapters() {
25195
25537
  // Initialize all adapters, do it in parallel
25196
25538
  const adapterEntries = Object.entries(this.#adapters);
25539
+ // Kick off every adapter's initialize() concurrently
25197
25540
  const initializationPromises = adapterEntries.map(([_identifier, adapter]) => adapter.initialize());
25198
25541
  // Wait for all adapters to settle (either resolve or reject)
25199
25542
  const results = await Promise.allSettled(initializationPromises);
25200
25543
  // Check each result and dispatch errors for failed initializations
25201
25544
  for (const [index, result] of results.entries()) {
25545
+ // Only rejected adapters need an error dispatched; fulfilled ones need no action
25202
25546
  if (result.status === 'rejected') {
25203
25547
  const [identifier] = adapterEntries[index];
25204
25548
  this._dispatchError(new Error(`Failed to initialize telemetry adapter "${identifier}"`, {
@@ -25215,8 +25559,10 @@ class TelemetryProvider extends BaseModuleProvider {
25215
25559
  *
25216
25560
  * @returns Subscription to the telemetry item stream
25217
25561
  * @protected
25562
+ * @throws {Error} When the provider has not been initialized yet.
25218
25563
  */
25219
25564
  _connectAdapters() {
25565
+ // Adapters can only be connected after the provider has completed initialization
25220
25566
  if (!this.#initialized) {
25221
25567
  throw new Error('TelemetryProvider is not initialized');
25222
25568
  }
@@ -25269,6 +25615,7 @@ class TelemetryProvider extends BaseModuleProvider {
25269
25615
  * @protected
25270
25616
  */
25271
25617
  _dispatchEntityEvent(item) {
25618
+ // Only dispatch when an event provider is configured
25272
25619
  if (this.#eventProvider) {
25273
25620
  // Wrap the telemetry item in a TelemetryEvent and dispatch it
25274
25621
  this.#eventProvider.dispatchEvent(new TelemetryEvent(item, this));
@@ -25281,6 +25628,7 @@ class TelemetryProvider extends BaseModuleProvider {
25281
25628
  * @protected
25282
25629
  */
25283
25630
  _dispatchError(error) {
25631
+ // Only dispatch when an event provider is configured
25284
25632
  if (this.#eventProvider) {
25285
25633
  this.#eventProvider.dispatchEvent(new TelemetryErrorEvent(error, this));
25286
25634
  }
@@ -25298,6 +25646,7 @@ class TelemetryProvider extends BaseModuleProvider {
25298
25646
  * });
25299
25647
  */
25300
25648
  track(item) {
25649
+ // Dispatch to the type-specific tracker so each item is validated with the right schema
25301
25650
  switch (item.type) {
25302
25651
  case TelemetryType.Event:
25303
25652
  this.trackEvent(item);
@@ -25410,6 +25759,7 @@ class TelemetryProvider extends BaseModuleProvider {
25410
25759
  */
25411
25760
  getAdapter(identifier) {
25412
25761
  const adapter = this.#adapters[identifier];
25762
+ // Report a missing adapter as a telemetry exception rather than throwing
25413
25763
  if (!adapter) {
25414
25764
  this.trackException({
25415
25765
  name: 'TelemetryAdapterNotFound',
@@ -25477,6 +25827,7 @@ const module$3 = {
25477
25827
  const mapConfiguratorEvents = (
25478
25828
  // biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
25479
25829
  configurator) => {
25830
+ // Map each configurator event into a standardized telemetry item shape
25480
25831
  return configurator.event$.pipe(map$1((event) => {
25481
25832
  const item = {
25482
25833
  name: event.name,
@@ -25488,6 +25839,7 @@ configurator) => {
25488
25839
  },
25489
25840
  scope: ['configuration'],
25490
25841
  };
25842
+ // An event carrying an error is reported as a telemetry exception
25491
25843
  if (event.error) {
25492
25844
  return {
25493
25845
  ...item,
@@ -25495,6 +25847,7 @@ configurator) => {
25495
25847
  exception: event.error,
25496
25848
  };
25497
25849
  }
25850
+ // An event carrying a metric value is reported as a telemetry metric
25498
25851
  if (event.metric) {
25499
25852
  return {
25500
25853
  ...item,
@@ -25544,9 +25897,11 @@ configurator, options) => {
25544
25897
  configurator.addConfig({
25545
25898
  module: module$3,
25546
25899
  configure: async (builder, ref) => {
25900
+ // Attach configurator events to the telemetry builder only when opted in
25547
25901
  {
25548
25902
  builder.attachItems(mapConfiguratorEvents(configurator));
25549
25903
  }
25904
+ // Run the caller-supplied configure callback when one was provided
25550
25905
  if (options?.configure) {
25551
25906
  await Promise.resolve(options.configure(builder, ref));
25552
25907
  }
@@ -42493,11 +42848,14 @@ class MsalClient extends PublicClientApplication {
42493
42848
  * - **Redirect**: Navigates browser to Microsoft login page. Returns `void` because the browser
42494
42849
  * navigates to a new page. After redirect completes, the result will be available via
42495
42850
  * `handleRedirectPromise()` when the app loads on the new page.
42851
+ *
42852
+ * @throws {Error} If an invalid `options.behavior` value is provided.
42496
42853
  */
42497
42854
  async login(options) {
42498
42855
  // Attempt silent authentication first if enabled
42499
42856
  // This provides better UX by avoiding unnecessary popups/redirects
42500
42857
  if (options.silent) {
42858
+ // Warn early when neither an account nor login hint is available for silent SSO
42501
42859
  if (!options.request.account && !options.request.loginHint) {
42502
42860
  this.getLogger().warning('No account or login hint provided, please provide an account or login hint in the request', options.request.correlationId || FUSION_CORRELATION_ID);
42503
42861
  }
@@ -42555,6 +42913,7 @@ class MsalClient extends PublicClientApplication {
42555
42913
  * ```
42556
42914
  */
42557
42915
  async logout(options) {
42916
+ // Warn when no account was supplied since the active account will be used instead
42558
42917
  if (!options?.account) {
42559
42918
  this.getLogger().warning('No account available for logout, please provide an account in the options', FUSION_CORRELATION_ID);
42560
42919
  }
@@ -42586,18 +42945,23 @@ class MsalClient extends PublicClientApplication {
42586
42945
  *
42587
42946
  * The default silent behavior is determined by presence of account in the request.
42588
42947
  * This provides optimal UX by minimizing unnecessary user interactions.
42948
+ *
42949
+ * @throws {Error} If no `request` is provided in `options`.
42589
42950
  */
42590
42951
  async acquireToken(options) {
42591
42952
  const { behavior = 'redirect', silent = !!options.request?.account, request } = options;
42953
+ // A request is required to know which scopes/account to acquire a token for
42592
42954
  if (!request) {
42593
42955
  throw new Error('No request provided, please provide a request in the options');
42594
42956
  }
42957
+ // Warn when no scopes are requested, since MSAL requires at least one scope
42595
42958
  if (request.scopes.length === 0) {
42596
42959
  this.getLogger().warning('No scopes provided, please provide scopes in the request option, see options.request for more information.', request.correlationId || FUSION_CORRELATION_ID);
42597
42960
  }
42598
42961
  // Attempt silent token acquisition first
42599
42962
  // This fetches from cache or uses refresh token without user interaction
42600
42963
  if (silent) {
42964
+ // Only silent-acquire when an account is available to look up cached tokens for
42601
42965
  if (request.account) {
42602
42966
  try {
42603
42967
  this.getLogger().verbose('Attempting to acquire token silently', request.correlationId || FUSION_CORRELATION_ID);
@@ -42657,6 +43021,7 @@ const levelMap = {
42657
43021
  const parseMsalMessage = (message) => {
42658
43022
  // Match the structured format: [timestamp] : [correlationId] : [package] : [level] - [component] - [message]
42659
43023
  const match = message.match(/^\[([^\]]+)\]\s*:\s*\[([^\]]*)\]\s*:\s*([^:]+)\s*:\s*(\w+)\s*-\s*([^-]+)\s*-\s*(.*)$/);
43024
+ // A structured match means we can extract package/component/message parts
42660
43025
  if (match) {
42661
43026
  const [, _timestamp, _correlationId, packageInfo, _logLevel, component, logMessage] = match;
42662
43027
  return {
@@ -42719,7 +43084,7 @@ const createClientLogCallback = (provider, metadata, scope) => {
42719
43084
  };
42720
43085
 
42721
43086
  // Generated by genversion.
42722
- const version$2 = '10.0.0';
43087
+ const version$2 = '10.0.2';
42723
43088
 
42724
43089
  /**
42725
43090
  * Zod schema for telemetry configuration validation.
@@ -42772,6 +43137,7 @@ class MsalConfigurator extends BaseConfigBuilder {
42772
43137
  * The MSAL module version being configured.
42773
43138
  *
42774
43139
  * @default Latest
43140
+ * @returns The configured MSAL module version.
42775
43141
  */
42776
43142
  get version() {
42777
43143
  return version$2;
@@ -42787,6 +43153,7 @@ class MsalConfigurator extends BaseConfigBuilder {
42787
43153
  this._set('version', async () => this.version);
42788
43154
  // Auto-detect and integrate telemetry module if available
42789
43155
  this._set('telemetry.provider', async (args) => {
43156
+ // Only resolve the telemetry instance when the telemetry module is registered
42790
43157
  if (args.hasModule('telemetry')) {
42791
43158
  const telemetry = await args.requireInstance('telemetry');
42792
43159
  return telemetry;
@@ -43010,7 +43377,7 @@ class MsalConfigurator extends BaseConfigBuilder {
43010
43377
  const config = await MsalConfigSchema.parseAsync(rawConfig);
43011
43378
  // Auto-create client if config provided but no client instance
43012
43379
  // This allows users to provide configuration without manually instantiating the client
43013
- if (!config.client && !!this.#msalConfig) {
43380
+ if (!config.client && this.#msalConfig) {
43014
43381
  const clientConfig = this.#msalConfig;
43015
43382
  config.telemetry.provider?.trackEvent({
43016
43383
  name: 'module-msal.configurator._processConfig.creating-client',
@@ -43179,12 +43546,15 @@ class VersionError extends Error {
43179
43546
  function mapVersionToEnumVersion(version) {
43180
43547
  console.log('Resolving version:', version);
43181
43548
  const coercedVersion = semver.coerce(version);
43549
+ // An uncoercible version string cannot be mapped to a module version
43182
43550
  if (!coercedVersion) {
43183
43551
  throw new Error(`Invalid version: ${version}`);
43184
43552
  }
43553
+ // Versions before 4.0.0 (including 2.x) map to the v2-compatible module version
43185
43554
  if (semver.satisfies(coercedVersion, '<4.0.0')) {
43186
43555
  return MsalModuleVersion.V2;
43187
43556
  }
43557
+ // Versions before 7.0.0 (4.x and 5.x/6.x) map to the v4-compatible module version
43188
43558
  if (semver.satisfies(coercedVersion, '<7.0.0')) {
43189
43559
  return MsalModuleVersion.V4;
43190
43560
  }
@@ -43247,6 +43617,7 @@ function resolveVersion(version) {
43247
43617
  warnings.push(missingVersionWarning.message);
43248
43618
  wantedVersion = latestVersion;
43249
43619
  }
43620
+ // Warn when the requested major version trails the latest major version
43250
43621
  if (wantedVersion.major < latestVersion.major) {
43251
43622
  const majorBehindVersionWarning = new VersionError(`Requested major version ${wantedVersion.major} is behind the latest major version ${latestVersion.major}`, wantedVersion, latestVersion);
43252
43623
  warnings.push(majorBehindVersionWarning.message);
@@ -43341,9 +43712,11 @@ function mapAuthenticationResult(result) {
43341
43712
  function createProxyClient(client) {
43342
43713
  const proxy = new Proxy(client, {
43343
43714
  get: (target, prop) => {
43715
+ // Adapt each v4 client member to the v2-compatible shape expected by callers
43344
43716
  switch (prop) {
43345
43717
  case 'getAllAccounts': {
43346
43718
  return () => {
43719
+ // Map each v4 account shape to its v2-compatible equivalent
43347
43720
  return target.getAllAccounts().map(mapAccountInfo);
43348
43721
  };
43349
43722
  }
@@ -43375,6 +43748,7 @@ function createProxyClient(client) {
43375
43748
  case 'handleRedirectPromise': {
43376
43749
  return async () => {
43377
43750
  const result = await target.handleRedirectPromise();
43751
+ // No redirect result means there's nothing pending to map
43378
43752
  if (!result) {
43379
43753
  return null;
43380
43754
  }
@@ -43384,6 +43758,7 @@ function createProxyClient(client) {
43384
43758
  case 'getActiveAccount': {
43385
43759
  return () => {
43386
43760
  const account = target.getActiveAccount();
43761
+ // No active account means there's nothing to map
43387
43762
  if (!account) {
43388
43763
  return null;
43389
43764
  }
@@ -43470,6 +43845,8 @@ function createProxyClient(client) {
43470
43845
  }
43471
43846
  },
43472
43847
  });
43848
+ // The Proxy handler above implements every member of `IAuthClient` at runtime via its `get`
43849
+ // trap, but TypeScript can't verify a `Proxy<T>` satisfies `T` structurally.
43473
43850
  return proxy;
43474
43851
  }
43475
43852
 
@@ -43480,6 +43857,7 @@ function createProxyClient(client) {
43480
43857
  * @returns True if the request is in v4 format (has a `request` property with `scopes` and `account`)
43481
43858
  */
43482
43859
  function isRequestV4(req) {
43860
+ // Non-object/null values can never match the v4 request shape
43483
43861
  if (typeof req !== 'object' || req === null) {
43484
43862
  return false;
43485
43863
  }
@@ -43513,6 +43891,7 @@ function createProxyProvider$2(provider) {
43513
43891
  // Use Proxy to intercept property access and provide v2-compatible implementations
43514
43892
  const proxy = new Proxy(provider, {
43515
43893
  get: (target, prop) => {
43894
+ // Adapt each v4 provider member to the v2-compatible shape expected by callers
43516
43895
  switch (prop) {
43517
43896
  case 'version': {
43518
43897
  return provider.version;
@@ -43527,6 +43906,7 @@ function createProxyProvider$2(provider) {
43527
43906
  case 'defaultClient': {
43528
43907
  // Deprecated property - redirect to client with warning
43529
43908
  console.warn('defaultClient is deprecated, use client instead');
43909
+ // Same v2-compatible client wrapper as the `client` case above.
43530
43910
  return v2Client;
43531
43911
  }
43532
43912
  case 'defaultAccount': {
@@ -43610,6 +43990,7 @@ function createProxyProvider$2(provider) {
43610
43990
  };
43611
43991
  }
43612
43992
  default: {
43993
+ // The proxy is awaited in some contexts, so `then` must resolve to undefined
43613
43994
  if (prop === 'then') {
43614
43995
  return undefined;
43615
43996
  }
@@ -43647,6 +44028,7 @@ function createProxyProvider$1(provider) {
43647
44028
  // Create passthrough proxy that only overrides msalVersion
43648
44029
  return new Proxy(provider, {
43649
44030
  get: (target, prop) => {
44031
+ // Passthrough every property except msalVersion, which reports V4 compatibility
43650
44032
  switch (prop) {
43651
44033
  case 'msalVersion': {
43652
44034
  // Report as V4 for compatibility tracking
@@ -43708,6 +44090,9 @@ function createProxyProvider$1(provider) {
43708
44090
  * @param version - The target version string (e.g., '2.0.0', '4.0.0')
43709
44091
  * @returns A proxy provider compatible with the specified version
43710
44092
  *
44093
+ * @template T - The provider interface type expected by the target version.
44094
+ * @throws {Error} If the resolved version is not supported.
44095
+ *
43711
44096
  * @example
43712
44097
  * ```typescript
43713
44098
  * const baseProvider = new MsalProvider(config);
@@ -43717,6 +44102,7 @@ function createProxyProvider$1(provider) {
43717
44102
  function createProxyProvider(provider, version) {
43718
44103
  // Resolve the requested version to determine which proxy to create
43719
44104
  const { enumVersion } = resolveVersion(version);
44105
+ // Build the version-appropriate proxy based on the resolved MSAL module version
43720
44106
  switch (enumVersion) {
43721
44107
  case MsalModuleVersion.V2:
43722
44108
  // Create v2-compatible proxy with legacy API adapters
@@ -43728,6 +44114,7 @@ function createProxyProvider(provider, version) {
43728
44114
  // Create transparent proxy for v5 - passes through to original provider
43729
44115
  return new Proxy(provider, {
43730
44116
  get: (target, prop) => {
44117
+ // Delegate every known property/method to the underlying provider
43731
44118
  switch (prop) {
43732
44119
  case 'version': {
43733
44120
  return target.version;
@@ -43814,6 +44201,8 @@ class MsalProvider extends BaseModuleProvider {
43814
44201
  * Default OAuth scopes used when the caller provides no scopes.
43815
44202
  *
43816
44203
  * Resolves to the app's Entra ID configured permissions via the `/.default` scope.
44204
+ *
44205
+ * @returns The default OAuth scopes derived from the configured client ID.
43817
44206
  */
43818
44207
  get defaultScopes() {
43819
44208
  const clientId = this.#client.clientId;
@@ -43836,6 +44225,8 @@ class MsalProvider extends BaseModuleProvider {
43836
44225
  *
43837
44226
  * Provides access to the underlying MSAL PublicClientApplication for advanced use cases.
43838
44227
  * Prefer using provider methods for standard authentication operations.
44228
+ *
44229
+ * @returns The underlying MSAL client instance.
43839
44230
  */
43840
44231
  get client() {
43841
44232
  return this.#client;
@@ -43845,6 +44236,8 @@ class MsalProvider extends BaseModuleProvider {
43845
44236
  *
43846
44237
  * Returns the active account if a user is authenticated, or null if no user is logged in.
43847
44238
  * This is a shorthand for `client.getActiveAccount()`.
44239
+ *
44240
+ * @returns The currently authenticated account, or `null` if no user is logged in.
43848
44241
  */
43849
44242
  get account() {
43850
44243
  return this.#client.getActiveAccount();
@@ -43908,6 +44301,8 @@ class MsalProvider extends BaseModuleProvider {
43908
44301
  *
43909
44302
  * The provider will attempt automatic login with empty scopes if requiresAuth is true.
43910
44303
  * Apps should call acquireToken with actual scopes after initialization completes.
44304
+ *
44305
+ * @throws {Error} If auth code exchange requires a client ID but none is configured.
43911
44306
  */
43912
44307
  async initialize() {
43913
44308
  // Guard: skip authentication when running inside MSAL's hidden iframe.
@@ -43934,6 +44329,7 @@ class MsalProvider extends BaseModuleProvider {
43934
44329
  // Use MSAL's acquireTokenByCode to exchange backend auth code for tokens
43935
44330
  // This follows Microsoft's standard SPA Auth Code Flow pattern
43936
44331
  const clientId = this.#client.clientId;
44332
+ // A client ID is required to build the default scope used for the auth code exchange
43937
44333
  if (!clientId) {
43938
44334
  throw new Error('Client ID is required for auth code exchange');
43939
44335
  }
@@ -43977,6 +44373,7 @@ class MsalProvider extends BaseModuleProvider {
43977
44373
  // Priority 1: Check if returning from redirect-based authentication
43978
44374
  // This handles cases where user just completed a login/acquireToken via redirect
43979
44375
  const handleRedirectResult = await this.handleRedirect();
44376
+ // A returned account means the user just completed a redirect-based login
43980
44377
  if (handleRedirectResult?.account) {
43981
44378
  // Successfully authenticated via redirect - set as active account
43982
44379
  // This means the user was redirected to Microsoft and came back authenticated
@@ -43993,6 +44390,7 @@ class MsalProvider extends BaseModuleProvider {
43993
44390
  // Note: Using default scopes here as we don't know what scopes the app needs yet
43994
44391
  // App should call acquireToken with actual scopes after initialization
43995
44392
  const loginResult = await this.login({ request: { scopes: this.defaultScopes } });
44393
+ // Only set the active account when the automatic login actually returned one
43996
44394
  if (loginResult?.account) {
43997
44395
  // Automatic login successful - set as active account
43998
44396
  this.#client.setActiveAccount(loginResult.account);
@@ -44041,6 +44439,8 @@ class MsalProvider extends BaseModuleProvider {
44041
44439
  * @remark Empty scopes are currently tracked as telemetry exceptions but execution continues for monitoring purposes.
44042
44440
  * This behavior will be changed to throw exceptions once sufficient metrics are collected.
44043
44441
  *
44442
+ * @throws {Error} Re-throws any error encountered during token acquisition after tracking it via telemetry.
44443
+ *
44044
44444
  * @example
44045
44445
  * ```typescript
44046
44446
  * // Modern API format
@@ -44083,6 +44483,7 @@ class MsalProvider extends BaseModuleProvider {
44083
44483
  }
44084
44484
  // Handle empty scopes - currently monitoring for telemetry, will throw in future
44085
44485
  if (candidateScopes.length === 0) {
44486
+ // Fall back to the client-id-derived default scope when one is available
44086
44487
  if (defaultScopes.length > 0) {
44087
44488
  this._trackEvent('acquireToken.missing-scope.defaulted', TelemetryLevel.Warning, {
44088
44489
  properties: { ...telemetryProperties, defaultScopes },
@@ -44094,7 +44495,7 @@ class MsalProvider extends BaseModuleProvider {
44094
44495
  exception,
44095
44496
  properties: telemetryProperties,
44096
44497
  });
44097
- // TODO: throw exception when sufficient metrics are collected
44498
+ // TODO(#5113): throw exception when sufficient metrics are collected
44098
44499
  // This allows us to monitor how often empty scopes are provided before enforcing validation
44099
44500
  }
44100
44501
  }
@@ -44221,6 +44622,7 @@ class MsalProvider extends BaseModuleProvider {
44221
44622
  // Fall through to interactive authentication
44222
44623
  }
44223
44624
  }
44625
+ // Perform interactive authentication based on specified behavior
44224
44626
  switch (behavior) {
44225
44627
  case 'popup':
44226
44628
  return await this.#client.loginPopup(request);
@@ -44302,6 +44704,7 @@ class MsalProvider extends BaseModuleProvider {
44302
44704
  async handleRedirect() {
44303
44705
  // Process any pending redirect from authentication flow
44304
44706
  const result = await this.client.handleRedirectPromise();
44707
+ // Only track/log when a redirect result is actually returned
44305
44708
  if (result) {
44306
44709
  // Track successful redirect completion for monitoring
44307
44710
  this._trackEvent('handleRedirect.success', TelemetryLevel.Information, {
@@ -44327,6 +44730,9 @@ class MsalProvider extends BaseModuleProvider {
44327
44730
  * - Version compatibility is tracked via telemetry
44328
44731
  * - Throws error if unsupported version is requested
44329
44732
  *
44733
+ * @template T - The provider interface type expected by the target version.
44734
+ * @throws {Error} If the requested version cannot be resolved or the proxy fails to create.
44735
+ *
44330
44736
  * @example
44331
44737
  * ```typescript
44332
44738
  * // Create v2-compatible proxy
@@ -44456,6 +44862,7 @@ const module$2 = {
44456
44862
  // Priority 2: Check if provider exists in parent module (proxy compatibility)
44457
44863
  // This allows child applications to reuse parent's authentication provider
44458
44864
  const hostProvider = init.ref?.auth;
44865
+ // Reuse the parent's provider (via a version-compatible proxy) when available
44459
44866
  if (hostProvider) {
44460
44867
  try {
44461
44868
  const proxyProvider = hostProvider.createProxyProvider(config.version);
@@ -44464,7 +44871,7 @@ const module$2 = {
44464
44871
  catch (error) {
44465
44872
  console.error('MsalModule::Failed to create proxy provider', error);
44466
44873
  // Fallback to host provider to prevent app breakage during migration
44467
- // TODO: Consider throwing error instead once all apps are migrated to v4
44874
+ // TODO(#5114): Consider throwing error instead once all apps are migrated to v4
44468
44875
  return hostProvider;
44469
44876
  }
44470
44877
  }
@@ -44499,7 +44906,7 @@ const module$2 = {
44499
44906
  * ```
44500
44907
  */
44501
44908
  const enableMSAL = (
44502
- // @biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
44909
+ // biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
44503
44910
  configurator, configure) => {
44504
44911
  const config = configure ? configureMsal(configure) : { module: module$2 };
44505
44912
  configurator.addConfig(config);
@@ -44533,7 +44940,11 @@ const configureMsal = (configure) => ({
44533
44940
  * @returns A function that takes an Observable stream of `QueryQueueItem` and returns an Observable
44534
44941
  * stream where each item is processed in sequence by the provided callback.
44535
44942
  */
44536
- const concatQueue = (...args) => (source$) => source$.pipe(concatMap(...args));
44943
+ const concatQueue = (...args) => (source$) => {
44944
+ // process queue items one after another, waiting for each to complete before starting the next
44945
+ return source$.pipe(concatMap(...args));
44946
+ };
44947
+
44537
44948
  /**
44538
44949
  * Takes a function that transforms each item in a queue and returns an Observable.
44539
44950
  * It processes each item concurrently, potentially leading to out-of-order execution.
@@ -44544,7 +44955,10 @@ const concatQueue = (...args) => (source$) => source$.pipe(concatMap(...args));
44544
44955
  * @returns A function that takes an Observable stream of `QueryQueueItem` and returns an Observable
44545
44956
  * stream where each item is processed concurrently by the provided callback.
44546
44957
  */
44547
- const mergeQueue = (...args) => (source$) => source$.pipe(mergeMap(...args));
44958
+ const mergeQueue = (...args) => (source$) =>
44959
+ // process all queue items concurrently, without waiting for previous items to complete
44960
+ source$.pipe(mergeMap(...args));
44961
+
44548
44962
  /**
44549
44963
  * Takes a function that transforms each item in a queue and returns an Observable.
44550
44964
  * It processes each item by cancelling the current task if a new one arrives.
@@ -44556,7 +44970,10 @@ const mergeQueue = (...args) => (source$) => source$.pipe(mergeMap(...args));
44556
44970
  * stream where each item is processed by the provided callback, but only the latest
44557
44971
  * item's result is emitted.
44558
44972
  */
44559
- const switchQueue = (...args) => (source$) => source$.pipe(switchMap(...args));
44973
+ const switchQueue = (...args) => (source$) =>
44974
+ // cancel the previous in-flight item whenever a new item arrives
44975
+ source$.pipe(switchMap(...args));
44976
+
44560
44977
  /**
44561
44978
  * Transforms a query result Observable into a plain value Observable by extracting the `value` property.
44562
44979
  *
@@ -44579,7 +44996,9 @@ const switchQueue = (...args) => (source$) => source$.pipe(switchMap(...args));
44579
44996
  * });
44580
44997
  * ```
44581
44998
  */
44582
- const queryValue = (source$) => source$.pipe(map$1((entry) => entry.value));
44999
+ const queryValue = (source$) =>
45000
+ // strip query metadata, keeping only the raw data value
45001
+ source$.pipe(map$1((entry) => entry.value));
44583
45002
 
44584
45003
  var REGEX = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
44585
45004
 
@@ -44788,6 +45207,12 @@ v5.URL = URL$1;
44788
45207
  class QueryClientError extends Error {
44789
45208
  type;
44790
45209
  request;
45210
+ /**
45211
+ * Creates a new `QueryClientError`.
45212
+ *
45213
+ * @param type - Whether this error represents a standard failure or an aborted request.
45214
+ * @param args - The error message, optional underlying cause, and optional request context.
45215
+ */
44791
45216
  constructor(type, args) {
44792
45217
  super(args.message, { cause: args.cause });
44793
45218
  this.type = type;
@@ -44855,14 +45280,19 @@ const actions$1 = createActions$1();
44855
45280
  * @param action$ - The stream of actions being dispatched in the system.
44856
45281
  * @returns An Observable that emits execute actions for each request action.
44857
45282
  */
44858
- const handleRequests = (action$) => action$.pipe(filter(actions$1.request.match), map$1((action) => actions$1.execute(action.meta.transaction)));
45283
+ const handleRequests = (action$) =>
45284
+ // convert each incoming request action into an execute action for its transaction
45285
+ action$.pipe(filter(actions$1.request.match), map$1((action) => actions$1.execute(action.meta.transaction)));
45286
+
44859
45287
  /**
44860
45288
  * Handles the execution of a query.
44861
45289
  *
44862
45290
  * @param fetch - The function used to execute the query.
44863
45291
  * @returns A flow that handles the execution of the query.
44864
45292
  */
44865
- const handleExecution = (fetch) => (action$, state$) => action$.pipe(filter(actions$1.execute.match), withLatestFrom(state$), mergeMap(([action, state]) => {
45293
+ const handleExecution = (fetch) => (action$, state$) =>
45294
+ // execute the query for each request and translate the outcome back into actions
45295
+ action$.pipe(filter(actions$1.execute.match), withLatestFrom(state$), mergeMap(([action, state]) => {
44866
45296
  const transaction = action.payload;
44867
45297
  const request = state[transaction];
44868
45298
  // Create an AbortController instance to manage cancellation.
@@ -44875,6 +45305,7 @@ const handleExecution = (fetch) => (action$, state$) => action$.pipe(filter(acti
44875
45305
  }
44876
45306
  }));
44877
45307
  try {
45308
+ // map the fetch outcome to a success/failure action, cancelling early if aborted
44878
45309
  return from(fetch(request.args, controller.signal)).pipe(map$1((value) => actions$1.execute.success({
44879
45310
  ...request,
44880
45311
  status: 'complete',
@@ -44889,6 +45320,7 @@ const handleExecution = (fetch) => (action$, state$) => action$.pipe(filter(acti
44889
45320
  return of(actions$1.execute.failure(err, transaction));
44890
45321
  }
44891
45322
  }));
45323
+
44892
45324
  /**
44893
45325
  * Handles execution failure by scheduling retries according to the provided retry options.
44894
45326
  * If the maximum number of retries is exceeded or no retry options are provided, it emits an error action.
@@ -44898,6 +45330,7 @@ const handleExecution = (fetch) => (action$, state$) => action$.pipe(filter(acti
44898
45330
  */
44899
45331
  const handleFailure = (config) => {
44900
45332
  return (action$, state$) => {
45333
+ // filter for execution failures and decide whether to retry or report an error
44901
45334
  return action$.pipe(
44902
45335
  // Filter for actions that indicate an execution failure.
44903
45336
  filter(actions$1.execute.failure.match),
@@ -44954,6 +45387,7 @@ const handleFailure = (config) => {
44954
45387
  filter(([_, state]) => !!state[transaction]),
44955
45388
  // If the request has been removed, throw an error.
44956
45389
  tap(([, state]) => {
45390
+ // Re-check inside the tap since the request could have been removed during the delay
44957
45391
  if (!state[transaction]) {
44958
45392
  throw new QueryClientError('error', {
44959
45393
  message: 'request not found, most likely removed while scheduled for retry',
@@ -45009,9 +45443,9 @@ const createReducer$1 = (initial = {}) => createReducer$2(initial, (builder) =>
45009
45443
  builder.addCase(actions$1.execute, (state, action) => {
45010
45444
  // Locates the query entry in the state using the transaction ID provided in `action.payload`.
45011
45445
  const entry = state[action.payload];
45446
+ // If the entry is found, logs the current timestamp to the `execution` array
45447
+ // to indicate when the execution action was triggered.
45012
45448
  if (entry) {
45013
- // If the entry is found, logs the current timestamp to the `execution` array
45014
- // to indicate when the execution action was triggered.
45015
45449
  entry.execution.push(Date.now());
45016
45450
  // Updates the status of the entry to 'active' to reflect that the execution
45017
45451
  // process has started.
@@ -45026,9 +45460,9 @@ const createReducer$1 = (initial = {}) => createReducer$2(initial, (builder) =>
45026
45460
  builder.addCase(actions$1.execute.failure, (state, action) => {
45027
45461
  // Locates the query entry in the state using the transaction ID provided in `action.meta`.
45028
45462
  const entry = state[action.meta.transaction];
45463
+ // If the entry is found, the error information from `action.payload.error` is added to the `errors` array of the entry.
45464
+ // This allows tracking of all errors that have occurred during the execution of the query.
45029
45465
  if (entry) {
45030
- // If the entry is found, the error information from `action.payload.error` is added to the `errors` array of the entry.
45031
- // This allows tracking of all errors that have occurred during the execution of the query.
45032
45466
  entry.errors.push(action.payload.error);
45033
45467
  // The status of the entry is updated to 'failed' to indicate that the query execution has encountered an error.
45034
45468
  entry.status = 'failed';
@@ -45063,6 +45497,9 @@ class QueryClientJob extends Observable {
45063
45497
  get status() {
45064
45498
  return this.#status$.value;
45065
45499
  }
45500
+ /**
45501
+ * An Observable stream of the job's status, emitting whenever the status changes.
45502
+ */
45066
45503
  get status$() {
45067
45504
  return this.#status$.asObservable();
45068
45505
  }
@@ -45182,8 +45619,10 @@ class QueryClientJob extends Observable {
45182
45619
  * This method should be called to gracefully end a job that has finished its task.
45183
45620
  * It will also attempt to cancel the job with a completion reason, transitioning its status to 'canceled'
45184
45621
  * if it was in an 'active' state but hadn't yet completed.
45622
+ * @param reason - Optional reason recorded for the completion/cancellation.
45185
45623
  */
45186
45624
  complete(reason) {
45625
+ // Only cancel/complete when the job isn't already in a terminal state
45187
45626
  if (!this.closed) {
45188
45627
  this.cancel(reason ?? `job: ${this.transaction} was completed`);
45189
45628
  }
@@ -45198,6 +45637,9 @@ class QueryClientJob extends Observable {
45198
45637
  const transaction = this.transaction;
45199
45638
  this.#client.cancel(transaction, reason ?? `job: ${transaction} was canceled`);
45200
45639
  }
45640
+ /**
45641
+ * Disposes of the job by completing it, releasing any held resources.
45642
+ */
45201
45643
  [Symbol.dispose]() {
45202
45644
  this.complete();
45203
45645
  }
@@ -45311,6 +45753,7 @@ class QueryClient extends Observable {
45311
45753
  * @returns {Observable<TType>} An Observable stream of successful results.
45312
45754
  */
45313
45755
  get success$() {
45756
+ // narrow the action stream down to successful execute actions and extract their payload
45314
45757
  return this.action$.pipe(filter(actions$1.execute.success.match), map$1(({ payload }) => payload));
45315
45758
  }
45316
45759
  /**
@@ -45319,6 +45762,7 @@ class QueryClient extends Observable {
45319
45762
  * @returns {Observable<QueryClientError>} An Observable stream of query errors.
45320
45763
  */
45321
45764
  get error$() {
45765
+ // narrow the action stream down to client errors and wrap them as QueryClientError instances
45322
45766
  return this.action$.pipe(filterAction('client/error'), withLatestFrom(this.#state), map$1(([action, state]) => {
45323
45767
  const { payload, meta: { transaction }, } = action;
45324
45768
  const request = state[transaction];
@@ -45349,7 +45793,9 @@ class QueryClient extends Observable {
45349
45793
  // Add flows to handle different aspects of the query lifecycle.
45350
45794
  this.#subscription.add(this.#state.addFlow(handleRequests));
45351
45795
  this.#subscription.add(this.#state.addFlow(handleExecution(queryFn)));
45352
- this.#subscription.add(this.#state.addFlow(handleFailure(Object.assign({ count: 0, delay: 0 }, options?.retry))));
45796
+ // merge caller-provided retry options over the defaults
45797
+ const retryOptions = Object.assign({ count: 0, delay: 0 }, options?.retry);
45798
+ this.#subscription.add(this.#state.addFlow(handleFailure(retryOptions)));
45353
45799
  /**
45354
45800
  * Register effects to emit lifecycle events for different query actions.
45355
45801
  *
@@ -45402,6 +45848,7 @@ class QueryClient extends Observable {
45402
45848
  */
45403
45849
  query(args, opt) {
45404
45850
  const job = new QueryClientJob(this, args, opt);
45851
+ // share a single underlying job execution across all subscribers, replaying past emissions to late subscribers
45405
45852
  const job$ = job.pipe(share({ connector: () => new ReplaySubject(), resetOnRefCountZero: false }));
45406
45853
  Object.defineProperties(job$, {
45407
45854
  transaction: { get: () => job.transaction },
@@ -45411,6 +45858,8 @@ class QueryClient extends Observable {
45411
45858
  cancel: { value: (reason) => job.cancel(reason) },
45412
45859
  complete: { value: () => job.complete() },
45413
45860
  });
45861
+ // `job$` is a plain shared Observable; the properties defined above give it the
45862
+ // `QueryClientJob` shape at runtime, which TypeScript can't infer structurally.
45414
45863
  return job$;
45415
45864
  }
45416
45865
  /**
@@ -45439,7 +45888,10 @@ class QueryClient extends Observable {
45439
45888
  * @returns The request matching the reference, or `undefined` if not found.
45440
45889
  */
45441
45890
  getRequestByRef(ref) {
45442
- return Object.values(this.#state.value).find((x) => x.ref === ref);
45891
+ const requests = Object.values(this.#state.value);
45892
+ // scan all tracked requests for the one matching the given ref
45893
+ const match = requests.find((x) => x.ref === ref);
45894
+ return match;
45443
45895
  }
45444
45896
  /**
45445
45897
  * Cancels a task by its reference string. If a reason is provided, it will be included
@@ -45449,6 +45901,7 @@ class QueryClient extends Observable {
45449
45901
  */
45450
45902
  cancelTaskByRef(ref, reason) {
45451
45903
  const entry = this.getRequestByRef(ref);
45904
+ // Only cancel when a matching request was actually found
45452
45905
  if (entry?.ref) {
45453
45906
  this.cancel(entry.transaction, reason);
45454
45907
  }
@@ -45460,6 +45913,7 @@ class QueryClient extends Observable {
45460
45913
  * @param {string} [reason] The reason for cancellation.
45461
45914
  */
45462
45915
  cancel(transaction, reason) {
45916
+ // No transaction targeted — cancel every tracked transaction
45463
45917
  if (!transaction) {
45464
45918
  // If no specific transaction is provided, iterate through all transactions
45465
45919
  // in the state and cancel each one, applying a generic cancellation reason.
@@ -45504,7 +45958,7 @@ class QueryClient extends Observable {
45504
45958
  * @template TArgs The type of the arguments associated with the cache entry.
45505
45959
  * @returns An object containing the cache actions.
45506
45960
  */
45507
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45961
+ // biome-ignore lint/suspicious/noExplicitAny: default must be bivariant `any`; `unknown` breaks assignability of concrete `Actions<TType, TArgs>` streams (e.g. in `Observable<Actions<TType, TArgs>>` subscriptions) to the default-typed generic
45508
45962
  function createActions() {
45509
45963
  return {
45510
45964
  /**
@@ -45596,6 +46050,7 @@ function createReducer (actions, initial = {}) {
45596
46050
  .addCase(actions.insert, (state, action) => {
45597
46051
  const { key, entry } = action.payload;
45598
46052
  const record = state[key];
46053
+ // Update the existing entry in place, or create a new one if none exists yet
45599
46054
  if (record) {
45600
46055
  // If the record exists, update the timestamp and increment the update count.
45601
46056
  record.updated = Date.now();
@@ -45623,8 +46078,10 @@ function createReducer (actions, initial = {}) {
45623
46078
  // Handles the 'invalidate' action to invalidate a cache record.
45624
46079
  .addCase(actions.invalidate, (state, action) => {
45625
46080
  const invalidKey = action.payload ? [action.payload] : Object.keys(state);
46081
+ // Invalidate either the single targeted key, or every key when none was specified
45626
46082
  for (const key of invalidKey) {
45627
46083
  const entry = state[key];
46084
+ // Only reset the timestamp when the record actually exists
45628
46085
  if (entry) {
45629
46086
  // reset the updated timestamp
45630
46087
  entry.updated = undefined;
@@ -45635,6 +46092,7 @@ function createReducer (actions, initial = {}) {
45635
46092
  .addCase(actions.mutate, (state, action) => {
45636
46093
  const { key, value, updated } = action.payload;
45637
46094
  const record = state[key];
46095
+ // Only apply the mutation when a record exists for this key
45638
46096
  if (record) {
45639
46097
  // Update the record with the new value and metadata.
45640
46098
  record.value = value;
@@ -45660,8 +46118,10 @@ function createReducer (actions, initial = {}) {
45660
46118
  .map(([key]) => key);
45661
46119
  // Remove keys that are not in the list of valid keys.
45662
46120
  if (currentKeys.length !== validKeys.length) {
46121
+ // Walk all current keys and drop the ones that were trimmed away
45663
46122
  for (const key of currentKeys) {
45664
46123
  const validKeyIndex = validKeys.indexOf(key);
46124
+ // The key survived trimming — remove it from the search list so later lookups are faster
45665
46125
  if (validKeyIndex !== -1) {
45666
46126
  // If the key is valid, remove it from the search list.
45667
46127
  validKeys.splice(validKeyIndex, 1);
@@ -45773,6 +46233,7 @@ class QueryCache {
45773
46233
  constructor(args) {
45774
46234
  const { trimming, initial } = args ?? {};
45775
46235
  this.#state = new FlowSubject(createReducer(actions, initial));
46236
+ // Only wire up automatic trimming when a trimming policy was provided
45776
46237
  if (trimming) {
45777
46238
  this.#state.addEffect('cache/set', () => this.#state.next(actions.trim(trimming)));
45778
46239
  }
@@ -45820,6 +46281,7 @@ class QueryCache {
45820
46281
  invalidate(key) {
45821
46282
  const item = key ? this.#state.value[key] : undefined;
45822
46283
  this.#state.next(actions.invalidate(key, item));
46284
+ // Only emit an event when a specific key was invalidated (not a full-cache clear)
45823
46285
  if (key) {
45824
46286
  this._registerEvent('query_cache_entry_invalidated', {
45825
46287
  key,
@@ -45831,9 +46293,12 @@ class QueryCache {
45831
46293
  * Mutates an item in the query cache by key.
45832
46294
  * @param {string} key - The key of the item to mutate.
45833
46295
  * @param {QueryCacheMutation | ((current: TType) => QueryCacheMutation)} changes - The changes to apply to the item.
46296
+ * @returns A function that reverts the mutation, restoring the previous value.
46297
+ * @throws {Error} When no cache entry exists for `key`.
45834
46298
  */
45835
46299
  mutate(key, changes) {
45836
46300
  const current = key in this.#state.value ? this.#state.value[key] : undefined;
46301
+ // A missing entry cannot be mutated — mutation only ever updates existing entries
45837
46302
  if (!current) {
45838
46303
  throw new Error(`Cannot mutate cache item with key ${key}: item not found`);
45839
46304
  }
@@ -45857,6 +46322,7 @@ class QueryCache {
45857
46322
  const beforeKeys = new Set(Object.keys(this.#state.value));
45858
46323
  this.#state.next(actions.trim(options));
45859
46324
  const afterKeys = new Set(Object.keys(this.#state.value));
46325
+ // keys that existed before trimming but not after were the ones removed
45860
46326
  const removedKeys = Array.from(beforeKeys).filter((key) => !afterKeys.has(key));
45861
46327
  this._registerEvent('query_cache_trimmed', {
45862
46328
  data: {
@@ -45926,6 +46392,7 @@ class QueryTask extends Subject {
45926
46392
  #created = Date.now();
45927
46393
  /**
45928
46394
  * Gets the creation timestamp of the query task.
46395
+ * @returns The Unix timestamp (in milliseconds) when the task was created.
45929
46396
  */
45930
46397
  get created() {
45931
46398
  return this.#created;
@@ -45933,6 +46400,7 @@ class QueryTask extends Subject {
45933
46400
  #uuid = v4();
45934
46401
  /**
45935
46402
  * Gets the unique identifier of the query task.
46403
+ * @returns The task's generated UUID.
45936
46404
  */
45937
46405
  get uuid() {
45938
46406
  return this.#uuid;
@@ -45958,6 +46426,7 @@ class QueryTask extends Subject {
45958
46426
  * @returns A subscription to the job's result.
45959
46427
  */
45960
46428
  processJob(job) {
46429
+ // map the job's completion into a QueryTaskCompleted record for subscribers
45961
46430
  return job
45962
46431
  .pipe(map$1((result) => {
45963
46432
  const { key, uuid, created } = this;
@@ -45990,6 +46459,13 @@ class QueryEvent {
45990
46459
  type;
45991
46460
  key;
45992
46461
  data;
46462
+ /**
46463
+ * Creates a new Query event.
46464
+ *
46465
+ * @param type - The specific event type identifier.
46466
+ * @param key - The cache key associated with this event.
46467
+ * @param data - Optional event-specific data payload.
46468
+ */
45993
46469
  constructor(type, key, data) {
45994
46470
  this.type = type;
45995
46471
  this.key = key;
@@ -46027,10 +46503,12 @@ const defaultCacheValidator = (expires = 0) => (entry) => (entry.updated ?? 0) +
46027
46503
  * @returns A function that takes a query request and returns an Observable representing the queued request.
46028
46504
  */
46029
46505
  const getQueueOperator = (type = 'switch') => {
46506
+ // A custom queue function was provided, use it directly
46030
46507
  if (typeof type === 'function') {
46031
46508
  return type;
46032
46509
  }
46033
46510
  return (() => {
46511
+ // Map the named operator type to its concrete implementation
46034
46512
  switch (type) {
46035
46513
  case 'concat':
46036
46514
  return concatQueue;
@@ -46101,7 +46579,6 @@ const getQueueOperator = (type = 'switch') => {
46101
46579
  * @see {@link Query.invalidate} for more details on invalidating cache entries.
46102
46580
  * @see {@link QueueOperatorType} for more details on the available queue operators.
46103
46581
  */
46104
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
46105
46582
  class Query {
46106
46583
  /**
46107
46584
  * Static utility that extracts the raw value from a query result Observable.
@@ -46161,16 +46638,19 @@ class Query {
46161
46638
  #event$;
46162
46639
  /**
46163
46640
  * A public getter for the client instance.
46164
- * TODO: Implement a proxy to control access to the client.
46641
+ * TODO(#5144): Implement a proxy to control access to the client.
46165
46642
  * This proxy would allow for additional functionality or restrictions when accessing the client instance.
46643
+ * @returns The internal `QueryClient` instance used to fetch data.
46166
46644
  */
46167
46645
  get client() {
46168
- // TODO: Proxy
46646
+ // TODO(#5144): Proxy
46169
46647
  return this.#client;
46170
46648
  }
46171
46649
  /**
46172
46650
  * Protected helper method to register events if an event observer is configured.
46651
+ * @template TKey - The event type key, constrained to the keys of `QueryEvents`.
46173
46652
  * @param type - The event type
46653
+ * @param key - The cache/query key the event relates to
46174
46654
  * @param data - The event data
46175
46655
  */
46176
46656
  _registerEvent(type, key, data) {
@@ -46178,11 +46658,12 @@ class Query {
46178
46658
  }
46179
46659
  /**
46180
46660
  * A public getter for the cache instance.
46181
- * TODO: Implement a proxy to control access to the cache.
46661
+ * TODO(#5144): Implement a proxy to control access to the cache.
46182
46662
  * This proxy would allow for additional functionality or restrictions when accessing the cache instance.
46663
+ * @returns The internal `QueryCache` instance used to cache query results.
46183
46664
  */
46184
46665
  get cache() {
46185
- // TODO: Proxy
46666
+ // TODO(#5144): Proxy
46186
46667
  return this.#cache;
46187
46668
  }
46188
46669
  /**
@@ -46247,6 +46728,7 @@ class Query {
46247
46728
  // shutdown the queue when the query is completed
46248
46729
  this.#subscription.add(() => this.#queue$.complete());
46249
46730
  this.#subscription.add(this.#queue$
46731
+ // register queued events, then apply the queue operator to the ongoing tasks
46250
46732
  .pipe(tap((key) => {
46251
46733
  this._registerEvent('query_queued', key);
46252
46734
  }),
@@ -46376,9 +46858,11 @@ class Query {
46376
46858
  const { skipResolve, ...args } = opt || {};
46377
46859
  const fn = skipResolve ? firstValueFrom : lastValueFrom;
46378
46860
  return new Promise((resolve, reject) => {
46861
+ // Reject the promise early if the caller aborts via the signal
46379
46862
  if (opt?.signal) {
46380
46863
  opt.signal.addEventListener('abort', () => reject(new Error('Query aborted')));
46381
46864
  }
46865
+ // Throw if the query completes without emitting a value
46382
46866
  fn(this._query(payload, args).pipe(throwIfEmpty())).then(resolve, reject);
46383
46867
  });
46384
46868
  }
@@ -46400,7 +46884,7 @@ class Query {
46400
46884
  persistentQuery(args, options) {
46401
46885
  const key = this.#generateCacheKey(args);
46402
46886
  const original = this._query(args, options);
46403
- return new Observable((subscriber) => {
46887
+ const cacheAwareStream = new Observable((subscriber) => {
46404
46888
  subscriber.add(original.subscribe({
46405
46889
  next: subscriber.next.bind(subscriber),
46406
46890
  error: subscriber.error.bind(subscriber),
@@ -46409,6 +46893,7 @@ class Query {
46409
46893
  const validateCache = options?.cache?.validate || this.#validateCacheEntry;
46410
46894
  // Subscribe to the cache state and filter for the specific cache entry based on the key.
46411
46895
  subscriber.add(this.cache.state$
46896
+ // narrow the cache state stream down to just this key's entry
46412
46897
  .pipe(filter((x) => key in x), map$1((x) => ({
46413
46898
  ...x[key],
46414
46899
  key,
@@ -46416,9 +46901,9 @@ class Query {
46416
46901
  hasValidCache: validateCache(x[key], args),
46417
46902
  })))
46418
46903
  .subscribe(subscriber));
46419
- }).pipe(
46904
+ });
46420
46905
  // only emit when the transaction changes
46421
- distinctUntilChanged((a, b) => a.transaction === b.transaction &&
46906
+ return cacheAwareStream.pipe(distinctUntilChanged((a, b) => a.transaction === b.transaction &&
46422
46907
  a.mutated === b.mutated));
46423
46908
  }
46424
46909
  /**
@@ -46438,10 +46923,15 @@ class Query {
46438
46923
  *
46439
46924
  * @param args - The arguments that identify the specific cache entry to be mutated.
46440
46925
  * @param changes - A function that defines the changes to be applied to the cache entry.
46926
+ * @param options - Optional settings; `allowCreation` controls whether a missing cache entry may be created.
46927
+ * @returns A function that reverts the mutation when called.
46928
+ * @throws {Error} When no cache entry exists for `args` and `options.allowCreation` is `undefined`.
46441
46929
  */
46442
46930
  mutate(args, changes, options) {
46443
46931
  const key = this.#generateCacheKey(args);
46932
+ // No existing cache entry for this key — decide whether to create one or fail
46444
46933
  if (key in this.cache.state === false) {
46934
+ // No explicit allowCreation setting provided — fail closed rather than silently creating
46445
46935
  if (options?.allowCreation === undefined) {
46446
46936
  throw new Error(`Cannot mutate cache item with key ${key}: item not found and option "allowCreation" is false`);
46447
46937
  }
@@ -46489,6 +46979,7 @@ class Query {
46489
46979
  * @returns A function that, when called, will unsubscribe the callback from further invalidation events.
46490
46980
  */
46491
46981
  onInvalidate(cb) {
46982
+ // only forward cache-invalidation actions to the callback
46492
46983
  const subscription = this.#cache.action$
46493
46984
  .pipe(filterAction('cache/invalidate'))
46494
46985
  .subscribe((action) => cb({ detail: { item: action.meta.item } }));
@@ -46503,6 +46994,7 @@ class Query {
46503
46994
  * @returns A function that, when called, will unsubscribe the callback from further mutation events.
46504
46995
  */
46505
46996
  onMutate(cb) {
46997
+ // only forward cache-mutation actions to the callback
46506
46998
  const subscription = this.#cache.action$
46507
46999
  .pipe(filterAction('cache/mutate'))
46508
47000
  .subscribe((action) => cb({ detail: { changes: action.payload, current: action.meta.item } }));
@@ -46560,7 +47052,9 @@ class Query {
46560
47052
  _createTask(key, args, options) {
46561
47053
  // Create a new Observable that represents the query task and will be returned to the caller.
46562
47054
  return new Observable((subscriber) => {
47055
+ // Support cancellation via an AbortSignal
46563
47056
  if (options?.signal) {
47057
+ // Already aborted before subscription — complete immediately without doing any work
46564
47058
  if (options?.signal.aborted) {
46565
47059
  this._registerEvent('query_aborted', key);
46566
47060
  return subscriber.complete();
@@ -46592,6 +47086,7 @@ class Query {
46592
47086
  // the status indicating that this is a cached response, and a flag indicating whether the cache
46593
47087
  // is considered valid based on the validation logic.
46594
47088
  subscriber.next(record);
47089
+ // Complete without fetching when the cache is valid, or invalid entries are intentionally suppressed
46595
47090
  if (hasValidCache || suppressInvalid) {
46596
47091
  this._registerEvent('query_completed', key, { data: cacheEntry.value, hasValidCache });
46597
47092
  // If the cache is valid, or if invalid cache entries should be suppressed (not re-fetched),
@@ -46605,6 +47100,7 @@ class Query {
46605
47100
  }
46606
47101
  // If the cache entry does not exist or is invalid, proceed to queue a new query request.
46607
47102
  const isExistingTask = key in this.#tasks;
47103
+ // No task is already tracking this key — create one and register it before queuing
46608
47104
  if (!isExistingTask) {
46609
47105
  this.#tasks[key] = new QueryTask(key, args, options);
46610
47106
  this._registerEvent('query_job_created', key, {
@@ -46683,7 +47179,9 @@ const serviceResponseSelector = (response, postProcess) => {
46683
47179
  const result$ = from(jsonSelector(response)).pipe(
46684
47180
  // parse and validate the response
46685
47181
  map$1((value) => ApiServices.default([]).parse(value)));
47182
+ // Only apply a transform pipeline when the caller supplied one
46686
47183
  if (postProcess) {
47184
+ // Apply the caller-supplied operator (e.g. session overrides) before emitting
46687
47185
  return result$.pipe(postProcess);
46688
47186
  }
46689
47187
  return result$;
@@ -46734,7 +47232,9 @@ class ServiceDiscoveryClient {
46734
47232
  /** {@inheritDoc IServiceDiscoveryClient.resolveService} */
46735
47233
  async resolveService(key, allow_cache) {
46736
47234
  const services = await this.resolveServices(allow_cache);
47235
+ // Locate the service matching the requested key
46737
47236
  const service = services.find((s) => s.key === key);
47237
+ // Fail loudly when the requested key doesn't resolve to a known service
46738
47238
  if (!service) {
46739
47239
  throw Error(`Failed to resolve service, invalid key [${key}]`);
46740
47240
  }
@@ -46775,6 +47275,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46775
47275
  * @throws {Error} When the HTTP module is not registered.
46776
47276
  */
46777
47277
  async _createConfig(init, initial) {
47278
+ // The service discovery client depends on the http module being registered
46778
47279
  if (!init.hasModule('http')) {
46779
47280
  throw new Error('http module is required');
46780
47281
  }
@@ -46782,6 +47283,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46782
47283
  if (!this._has('discoveryClient')) {
46783
47284
  // check if http module has a client with key 'service_discovery'
46784
47285
  const httpProvider = await init.requireInstance('http');
47286
+ // Auto-configure only when a matching http client has actually been registered
46785
47287
  if (httpProvider.hasClient('service_discovery')) {
46786
47288
  this.configureServiceDiscoveryClientByClientKey('service_discovery');
46787
47289
  }
@@ -46798,6 +47300,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46798
47300
  * @throws {Error} When `discoveryClient` has not been configured.
46799
47301
  */
46800
47302
  _processConfig(config, init) {
47303
+ // A discovery client is mandatory before the config can be considered complete
46801
47304
  if (!config.discoveryClient) {
46802
47305
  throw new Error('discoveryClient is required, please configure it');
46803
47306
  }
@@ -46852,6 +47355,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46852
47355
  configureServiceDiscoveryClient(configCallback) {
46853
47356
  this.setServiceDiscoveryClient(async (args) => {
46854
47357
  const { httpClient, endpoint } = (await lastValueFrom(from(configCallback(args)))) ?? {};
47358
+ // Only construct the client when the factory actually returned an http client
46855
47359
  if (httpClient) {
46856
47360
  return new ServiceDiscoveryClient({
46857
47361
  http: httpClient,
@@ -46867,7 +47371,9 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46867
47371
  // Check if there are any session overrides in session storage.
46868
47372
  try {
46869
47373
  const sessionOverrides = JSON.parse(storage.getItem('overriddenServiceDiscoveryUrls') || '{}');
47374
+ // Apply each session override onto the service matching its key
46870
47375
  for (const [key, { url, scopes }] of Object.entries(sessionOverrides)) {
47376
+ // Locate the service this override applies to
46871
47377
  const service = input.find((service) => service.key === key);
46872
47378
  // If the service can be found, override the values with the values
46873
47379
  // from session override.
@@ -46879,6 +47385,8 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46879
47385
  }
46880
47386
  }
46881
47387
  catch (e) {
47388
+ // Malformed or missing session storage overrides are not fatal — fall back to
47389
+ // the unmodified input and just log for visibility.
46882
47390
  console.error('Failed to JSON parse session overrides: "overriddenServiceDiscoveryUrls"', e);
46883
47391
  }
46884
47392
  return input;
@@ -46921,7 +47429,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46921
47429
  }
46922
47430
 
46923
47431
  // Generated by genversion.
46924
- const version$1 = '10.0.1';
47432
+ const version$1 = '10.0.2';
46925
47433
 
46926
47434
  /**
46927
47435
  * Default implementation of {@link IServiceDiscoveryProvider}.
@@ -46956,6 +47464,7 @@ class ServiceDiscoveryProvider extends BaseModuleProvider {
46956
47464
  /** {@inheritDoc IServiceDiscoveryProvider.createClient} */
46957
47465
  async createClient(name, opt) {
46958
47466
  const service = await this.resolveService(name);
47467
+ // A missing service configuration means the http client can't be constructed
46959
47468
  if (!service) {
46960
47469
  throw Error(`Could not load configuration of service [${name}]`);
46961
47470
  }
@@ -47135,7 +47644,7 @@ class BaseTelemetryAdapter {
47135
47644
  * without performing any initialization. It calls the protected `_initialize` method which
47136
47645
  * subclasses can override to provide custom initialization logic.
47137
47646
  *
47138
- * TODO: Consider changing return type from Promise<void> to Promise<TelemetryItem[]>
47647
+ * TODO(#5112): Consider changing return type from Promise<void> to Promise<TelemetryItem[]>
47139
47648
  * to allow adapters to return telemetry items (errors/warnings) that occurred during
47140
47649
  * initialization, eliminating the chicken-and-egg problem with reporting init errors.
47141
47650
  *
@@ -47202,6 +47711,7 @@ class ConsoleAdapter extends BaseTelemetryAdapter {
47202
47711
  * @returns CSS background color string
47203
47712
  */
47204
47713
  _generateTitleBackground(lvl) {
47714
+ // Pick a background color matching the severity of this telemetry level
47205
47715
  switch (lvl) {
47206
47716
  case TelemetryLevel.Critical:
47207
47717
  return 'rgb(255, 0, 0)';
@@ -47228,12 +47738,12 @@ class ConsoleAdapter extends BaseTelemetryAdapter {
47228
47738
  * @returns Formatted time string (ms, s, or m s format)
47229
47739
  */
47230
47740
  _formatMetric = (value) => {
47741
+ // Show milliseconds for values under 1 second
47231
47742
  if (value < 1000) {
47232
- // Show milliseconds for values under 1 second
47233
47743
  return `${Math.round(value)}ms`;
47234
47744
  }
47745
+ // Show seconds with 1 decimal for values under 1 minute
47235
47746
  if (value < 60000) {
47236
- // Show seconds with 1 decimal for values under 1 minute
47237
47747
  return `${(value / 1000).toFixed(1)}s`;
47238
47748
  }
47239
47749
  // For longer durations, show minutes and seconds
@@ -47275,6 +47785,7 @@ class ConsoleAdapter extends BaseTelemetryAdapter {
47275
47785
  metadata,
47276
47786
  scope,
47277
47787
  };
47788
+ // Dispatch to the right console format based on the telemetry item's type
47278
47789
  switch (type) {
47279
47790
  case TelemetryType.Event:
47280
47791
  // Log events with base data
@@ -47301,6 +47812,7 @@ class ConsoleAdapter extends BaseTelemetryAdapter {
47301
47812
  * @param msg - Additional message arguments to log
47302
47813
  */
47303
47814
  _log(lvl, title, ...msg) {
47815
+ // Pick the console method matching this telemetry level's severity
47304
47816
  switch (lvl) {
47305
47817
  case TelemetryLevel.Critical:
47306
47818
  case TelemetryLevel.Error:
@@ -47334,10 +47846,15 @@ class ConsoleAdapter extends BaseTelemetryAdapter {
47334
47846
  */
47335
47847
  const createPortalEntryPoint = (...segments) => {
47336
47848
  const normalized = segments
47849
+ // Drop undefined/null segments before further processing
47337
47850
  .filter((segment) => segment !== undefined && segment !== null)
47851
+ // Trim stray whitespace from each segment
47338
47852
  .map((segment) => segment.trim())
47853
+ // Drop any segments that became empty after trimming
47339
47854
  .filter(Boolean)
47855
+ // Strip leading slashes from every segment (and trailing slashes from the first)
47340
47856
  .map((segment, index) => {
47857
+ // The first segment may have a trailing slash (e.g. a base URL) that needs stripping
47341
47858
  if (index === 0) {
47342
47859
  return segment.replace(/\/+$/g, '');
47343
47860
  }
@@ -47378,6 +47895,7 @@ const createPortalEntryPoint = (...segments) => {
47378
47895
  */
47379
47896
  async function registerServiceWorker(framework) {
47380
47897
  const telemetry = framework.telemetry;
47898
+ // Bail out early when the browser has no service worker support at all
47381
47899
  if ('serviceWorker' in navigator === false) {
47382
47900
  const exception = new Error('Service workers are not supported in this browser.');
47383
47901
  exception.name = 'ServiceWorkerNotSupported';
@@ -47388,6 +47906,7 @@ async function registerServiceWorker(framework) {
47388
47906
  throw exception;
47389
47907
  }
47390
47908
  const resourceConfigs = import.meta.env.FUSION_SPA_SERVICE_WORKER_RESOURCES;
47909
+ // The worker needs a resource config to know which requests to intercept
47391
47910
  if (!resourceConfigs) {
47392
47911
  const exception = new Error('Service worker config is not defined.');
47393
47912
  exception.name = 'ServiceWorkerConfigNotDefined';
@@ -47411,10 +47930,12 @@ async function registerServiceWorker(framework) {
47411
47930
  navigator.serviceWorker.startMessages();
47412
47931
  // listen for messages from the service worker (set up before registration)
47413
47932
  navigator.serviceWorker.addEventListener('message', async (event) => {
47933
+ // Only the GET_TOKEN message type requests a token from this handler
47414
47934
  if (event.data.type === 'GET_TOKEN') {
47415
47935
  try {
47416
47936
  // extract scopes from the event data
47417
47937
  const scopes = event.data.scopes;
47938
+ // Scopes must be a real array before they can be used to request a token
47418
47939
  if (!scopes || !Array.isArray(scopes)) {
47419
47940
  const error = new Error('Invalid scopes provided');
47420
47941
  error.name = 'InvalidScopesProvided';
@@ -47422,6 +47943,7 @@ async function registerServiceWorker(framework) {
47422
47943
  }
47423
47944
  // request a token from the MSAL module
47424
47945
  const token = await framework.auth.acquireToken({ request: { scopes } });
47946
+ // A missing token means acquisition failed and the worker can't proceed
47425
47947
  if (!token) {
47426
47948
  const error = new Error('Failed to acquire token');
47427
47949
  error.name = 'FailedToAcquireToken';
@@ -47467,9 +47989,11 @@ async function registerServiceWorker(framework) {
47467
47989
  if (registration.waiting) {
47468
47990
  sendConfigToServiceWorker(registration.waiting);
47469
47991
  }
47992
+ // A worker still installing needs to reach the 'activated' state before it can receive config
47470
47993
  if (registration.installing) {
47471
47994
  registration.installing.addEventListener('statechange', (event) => {
47472
47995
  const worker = event.target;
47996
+ // Only send config once the worker has fully activated
47473
47997
  if (worker.state === 'activated') {
47474
47998
  sendConfigToServiceWorker(worker);
47475
47999
  }
@@ -47477,6 +48001,7 @@ async function registerServiceWorker(framework) {
47477
48001
  }
47478
48002
  // Listen for controller changes (happens during hard refresh or updates)
47479
48003
  navigator.serviceWorker.addEventListener('controllerchange', () => {
48004
+ // Only send config once this page is actually controlled by a worker
47480
48005
  if (navigator.serviceWorker.controller) {
47481
48006
  sendConfigToServiceWorker(navigator.serviceWorker.controller);
47482
48007
  }
@@ -47490,6 +48015,7 @@ async function registerServiceWorker(framework) {
47490
48015
  });
47491
48016
  // ensure we have an active service worker before sending config
47492
48017
  const activeWorker = readyRegistration.active;
48018
+ // Without an active worker there's nothing to send config to
47493
48019
  if (!activeWorker) {
47494
48020
  console.error('[Service Worker Registration] Service worker is not active after ready state');
47495
48021
  return;
@@ -47509,6 +48035,7 @@ async function registerServiceWorker(framework) {
47509
48035
  navigator.serviceWorker.addEventListener('controllerchange', onControllerChange);
47510
48036
  // Polling fallback and timeout to prevent infinite waiting
47511
48037
  checkInterval = setInterval(() => {
48038
+ // Stop polling once a controller has taken over
47512
48039
  if (navigator.serviceWorker.controller)
47513
48040
  finish();
47514
48041
  }, 200);
@@ -47536,7 +48063,7 @@ async function registerServiceWorker(framework) {
47536
48063
  }
47537
48064
 
47538
48065
  // Generated by genversion.
47539
- const version = '4.0.12';
48066
+ const version = '4.0.14';
47540
48067
 
47541
48068
  // Allow dynamic import without vite
47542
48069
  const importWithoutVite = (path) => import(/* @vite-ignore */ path);
@@ -47582,6 +48109,7 @@ enableTelemetry(configurator, {
47582
48109
  },
47583
48110
  // biome-ignore lint/suspicious/noExplicitAny: we need to use any here to allow dynamic properties
47584
48111
  };
48112
+ // Only attach user metadata when the auth module has resolved an account
47585
48113
  if (modules?.auth) {
47586
48114
  metadata.fusion.user = {
47587
48115
  id: modules.auth.account?.homeAccountId,
@@ -47646,8 +48174,8 @@ enableTelemetry(configurator, {
47646
48174
  document.body.innerHTML = '';
47647
48175
  document.body.appendChild(el);
47648
48176
  const portalEntryPoint = createPortalEntryPoint(portalProxy ? '/portal-proxy' : undefined, portal_manifest.build.assetPath, portal_manifest.build.templateEntry);
47649
- // @todo: should test if the entrypoint is external or internal
47650
- // @todo: add proper return type
48177
+ // TODO(#5066): should test if the entrypoint is external or internal
48178
+ // TODO(#5066): add proper return type
47651
48179
  const { render } = await measurement
47652
48180
  .clone()
47653
48181
  .resolve(importWithoutVite(portalEntryPoint), {