@equinor/fusion-framework-vite-plugin-spa 4.0.13 → 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 +28 -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 +723 -219
  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}`);
@@ -23451,24 +23644,6 @@ var objectTraps = {
23451
23644
  get(state, prop) {
23452
23645
  if (prop === DRAFT_STATE)
23453
23646
  return state;
23454
- if (prop === "constructor" || prop === "__proto__") {
23455
- const source2 = latest(state);
23456
- const value2 = source2[prop];
23457
- return new Proxy(value2 || {}, {
23458
- get: (target, key) => {
23459
- if (key === "__proto__" || key === "prototype") {
23460
- return Object.freeze(/* @__PURE__ */ Object.create(null));
23461
- }
23462
- return Reflect.get(target, key);
23463
- },
23464
- set: () => {
23465
- return true;
23466
- },
23467
- apply: (target, thisArg, args) => {
23468
- return Reflect.apply(target, thisArg, args);
23469
- }
23470
- });
23471
- }
23472
23647
  let arrayPlugin = state.scope_.arrayMethodsPlugin_;
23473
23648
  const isArrayWithStringProp = state.type_ === 1 /* Array */ && typeof prop === "string";
23474
23649
  if (isArrayWithStringProp) {
@@ -23489,7 +23664,7 @@ var objectTraps = {
23489
23664
  ) && isArrayIndex(prop)) {
23490
23665
  return value;
23491
23666
  }
23492
- if (value === peek(state.base_, prop)) {
23667
+ if (value === peek(state.base_, prop) || isRelocatedBaseRef(state, prop, value)) {
23493
23668
  prepareCopy(state);
23494
23669
  const childKey = state.type_ === 1 /* Array */ ? +prop : prop;
23495
23670
  const childDraft = createProxy(state.scope_, value, state, childKey);
@@ -23498,18 +23673,12 @@ var objectTraps = {
23498
23673
  return value;
23499
23674
  },
23500
23675
  has(state, prop) {
23501
- if (prop === "constructor" || prop === "__proto__" || prop === "prototype") {
23502
- return false;
23503
- }
23504
23676
  return prop in latest(state);
23505
23677
  },
23506
23678
  ownKeys(state) {
23507
23679
  return Reflect.ownKeys(latest(state));
23508
23680
  },
23509
23681
  set(state, prop, value) {
23510
- if (prop === "constructor" || prop === "__proto__" || prop === "prototype") {
23511
- return true;
23512
- }
23513
23682
  const desc = getDescriptorFromProto(latest(state), prop);
23514
23683
  if (desc?.set) {
23515
23684
  desc.set.call(state.draft_, value);
@@ -23598,6 +23767,12 @@ function peek(draft, prop) {
23598
23767
  const source = state ? latest(state) : draft;
23599
23768
  return source[prop];
23600
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
+ }
23601
23776
  function readPropFromProto(state, source, prop) {
23602
23777
  const desc = getDescriptorFromProto(source, prop);
23603
23778
  return desc ? VALUE in desc ? desc[VALUE] : (
@@ -23923,18 +24098,21 @@ class FlowSubject extends Observable {
23923
24098
  #state;
23924
24099
  /**
23925
24100
  * Observable stream of actions dispatched to the subject.
24101
+ * @returns An observable of dispatched actions.
23926
24102
  */
23927
24103
  get action$() {
23928
24104
  return this.#action.asObservable();
23929
24105
  }
23930
24106
  /**
23931
24107
  * The current value of state.
24108
+ * @returns The current state value.
23932
24109
  */
23933
24110
  get value() {
23934
24111
  return this.#state.value;
23935
24112
  }
23936
24113
  /**
23937
24114
  * Flag to indicate if the observable is closed.
24115
+ * @returns `true` if both the state and action subjects are closed.
23938
24116
  */
23939
24117
  get closed() {
23940
24118
  return this.#state.closed || this.#action.closed;
@@ -23951,6 +24129,7 @@ class FlowSubject extends Observable {
23951
24129
  });
23952
24130
  const initial = 'getInitialState' in reducer ? reducer.getInitialState() : initialState;
23953
24131
  this.#state = new BehaviorSubject(initial);
24132
+ // Reduce dispatched actions into state, skipping emissions that produce an unchanged value
23954
24133
  this.#action.pipe(scan(reducer, initial), distinctUntilChanged()).subscribe(this.#state);
23955
24134
  this.reset = () => this.#state.next(initial);
23956
24135
  }
@@ -23969,27 +24148,32 @@ class FlowSubject extends Observable {
23969
24148
  /**
23970
24149
  * Selects a derived state from the observable state and emits only when the selected state changes.
23971
24150
  *
24151
+ * @template T - The derived state type returned by the selector.
23972
24152
  * @param selector - A function that takes the current state and returns a derived state.
23973
24153
  * @param comparator - An optional function that compares the previous and current derived states to determine if a change has occurred.
23974
24154
  * @returns An observable that emits the derived state whenever it changes.
23975
24155
  */
23976
24156
  select(selector, comparator) {
24157
+ // Derive and de-duplicate the selected value from the state stream
23977
24158
  return this.#state.pipe(map$1(selector), distinctUntilChanged(comparator));
23978
24159
  }
23979
24160
  /**
23980
24161
  * Adds an effect that listens for actions and performs side effects.
23981
24162
  *
24163
+ * @template TType - The action type(s) to listen for.
23982
24164
  * @param actionTypeOrFn The type of action to listen for, an array of action types, or the effect function itself.
23983
24165
  * @param fn The effect function to execute when the action is dispatched, if the first parameter is an action type.
23984
24166
  * @returns A subscription to the effect.
23985
24167
  */
23986
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
23987
24170
  const action$ = typeof actionTypeOrFn === 'function'
23988
24171
  ? this.action$
23989
24172
  : Array.isArray(actionTypeOrFn)
23990
24173
  ? this.action$.pipe(filterAction(...actionTypeOrFn))
23991
24174
  : this.action$.pipe(filterAction(actionTypeOrFn));
23992
24175
  const mapper = (fn ? fn : actionTypeOrFn);
24176
+ // Run the effect for each matching action, then filter and reschedule any resulting action
23993
24177
  return action$
23994
24178
  .pipe(mergeMap((action) => from(new Promise((resolve, reject) => {
23995
24179
  try {
@@ -23998,7 +24182,9 @@ class FlowSubject extends Observable {
23998
24182
  catch (err) {
23999
24183
  reject(err);
24000
24184
  }
24001
- })).pipe(catchError((err) => {
24185
+ }))
24186
+ // Swallow effect errors so a single failing effect doesn't tear down the subscription
24187
+ .pipe(catchError((err) => {
24002
24188
  console.warn('unhandled effect', err);
24003
24189
  return EMPTY;
24004
24190
  }))), filter((x) => !!x), observeOn(asyncScheduler))
@@ -24040,9 +24226,11 @@ class FlowSubject extends Observable {
24040
24226
  */
24041
24227
  addFlow(fn) {
24042
24228
  const epic$ = fn(this.action$, this);
24229
+ // Fail fast if the flow function forgot to return an observable
24043
24230
  if (!epic$) {
24044
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!`);
24045
24232
  }
24233
+ // Log and swallow flow errors so a single failing flow doesn't tear down the subject
24046
24234
  return epic$
24047
24235
  .pipe(catchError((err) => {
24048
24236
  console.trace('unhandled exception, epic closed!', err);
@@ -24078,18 +24266,38 @@ class FlowSubject extends Observable {
24078
24266
  }
24079
24267
  }
24080
24268
 
24081
- /* 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
24082
24280
  /**
24083
24281
  * taken from https://github.com/reduxjs/redux-toolkit/tree/master/packages/toolkit/src
24084
24282
  */
24085
- // 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
+ */
24086
24291
  function createAction(type, prepareAction) {
24087
24292
  function actionCreator(...args) {
24293
+ // Use the prepare callback when the caller needs custom payload construction.
24088
24294
  if (prepareAction) {
24089
24295
  const prepared = prepareAction(...args);
24296
+ // Reject invalid prepare callbacks before reading their action fields.
24090
24297
  if (!prepared) {
24091
24298
  throw new Error('prepareAction did not return an object');
24092
24299
  }
24300
+ // Merge optional metadata and error fields into the prepared action.
24093
24301
  return {
24094
24302
  type,
24095
24303
  payload: prepared.payload,
@@ -24104,35 +24312,50 @@ function createAction(type, prepareAction) {
24104
24312
  actionCreator.match = (action) => action.type === type;
24105
24313
  return actionCreator;
24106
24314
  }
24107
- const actionSuffixDivider = '::';
24108
24315
 
24109
- /* 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
+ */
24110
24326
  function createAsyncAction(type, request, success, failure) {
24111
24327
  const action = createAction([type, 'request'].join(actionSuffixDivider), request);
24328
+ // Attach the success action only when the caller supplied its prepare function.
24112
24329
  if (success) {
24330
+ // Add the success action while preserving the request creator's identity.
24113
24331
  Object.assign(action, {
24114
24332
  success: createAction([type, 'success'].join(actionSuffixDivider), success),
24115
24333
  });
24116
24334
  }
24335
+ // Attach the failure action only for async workflows that support failure results.
24117
24336
  if (failure) {
24337
+ // Add the failure action alongside the existing request and success actions.
24118
24338
  Object.assign(action, {
24119
24339
  failure: createAction([type, 'failure'].join(actionSuffixDivider), failure),
24120
24340
  });
24121
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.
24122
24344
  return action;
24123
24345
  }
24124
24346
 
24125
24347
  function freezeDraftable(val) {
24126
- // biome-ignore lint/suspicious/noEmptyBlockStatements: This is a valid use case for an empty block statement
24127
24348
  return isDraftable(val) ? produce(val, () => { }) : val;
24128
24349
  }
24129
24350
  function isStateFunction(x) {
24130
24351
  return typeof x === 'function';
24131
24352
  }
24353
+ /** @inheritdoc */
24132
24354
  function createReducer$2(initialState, mapOrBuilderCallback) {
24133
24355
  const [actionsMap, finalActionMatchers, finalDefaultCaseReducer] = executeReducerBuilderCallback(mapOrBuilderCallback);
24134
24356
  // Ensure the initial state gets frozen either way (if draftable)
24135
24357
  let getInitialState;
24358
+ // A lazy initializer must be invoked before its result can be frozen
24136
24359
  if (isStateFunction(initialState)) {
24137
24360
  getInitialState = () => freezeDraftable(initialState());
24138
24361
  }
@@ -24143,19 +24366,31 @@ function createReducer$2(initialState, mapOrBuilderCallback) {
24143
24366
  function reducer(state, action) {
24144
24367
  let caseReducers = [
24145
24368
  actionsMap[action.type],
24146
- ...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),
24147
24374
  ];
24148
- 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) {
24149
24380
  caseReducers = [finalDefaultCaseReducer];
24150
24381
  }
24382
+ // Run each matched case reducer against the previous state, producing the next state
24151
24383
  return caseReducers.reduce((previousState, caseReducer) => {
24384
+ // Only transform state when a case reducer was matched for this slot
24152
24385
  if (caseReducer) {
24386
+ // Reuse an existing draft when already inside a `createNextState` call
24153
24387
  if (isDraft(previousState)) {
24154
24388
  // If it's already a draft, we must already be inside a `createNextState` call,
24155
24389
  // likely because this is being wrapped in `createReducer`, `createSlice`, or nested
24156
24390
  // inside an existing draft. It's safe to just pass the draft to the mutator.
24157
24391
  const draft = previousState; // We can assume this is already a draft
24158
24392
  const result = caseReducer(draft, action);
24393
+ // Treat an undefined result as "no change" rather than clearing the state
24159
24394
  if (result === undefined) {
24160
24395
  return previousState;
24161
24396
  }
@@ -24165,7 +24400,9 @@ function createReducer$2(initialState, mapOrBuilderCallback) {
24165
24400
  // If state is not draftable (ex: a primitive, such as 0), we want to directly
24166
24401
  // return the caseReducer func and not wrap it with produce.
24167
24402
  const result = caseReducer(previousState, action);
24403
+ // Treat an undefined result as "no change", unless there's no previous state to fall back to
24168
24404
  if (result === undefined) {
24405
+ // A `null` previous state has nothing to fall back to, so undefined is legitimately "no change"
24169
24406
  if (previousState === null) {
24170
24407
  return previousState;
24171
24408
  }
@@ -24175,7 +24412,7 @@ function createReducer$2(initialState, mapOrBuilderCallback) {
24175
24412
  }
24176
24413
  else {
24177
24414
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
24178
- // @ts-ignore
24415
+ // @ts-expect-error
24179
24416
  // createNextState() produces an Immutable<Draft<S>> rather
24180
24417
  // than an Immutable<S>, and TypeScript cannot find out how to reconcile
24181
24418
  // these two types.
@@ -24190,12 +24427,22 @@ function createReducer$2(initialState, mapOrBuilderCallback) {
24190
24427
  reducer.getInitialState = getInitialState;
24191
24428
  return reducer;
24192
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
+ */
24193
24439
  function executeReducerBuilderCallback(builderCallback) {
24194
24440
  const actionsMap = {};
24195
24441
  const actionMatchers = [];
24196
24442
  let defaultCaseReducer;
24197
24443
  const builder = {
24198
24444
  addCase(typeOrActionCreator, reducer) {
24445
+ // Skip these dev-only ordering checks in production builds to avoid the extra overhead
24199
24446
  if (process.env.NODE_ENV !== 'production') {
24200
24447
  /*
24201
24448
  * to keep the definition by the user in line with actual behavior,
@@ -24205,11 +24452,13 @@ function executeReducerBuilderCallback(builderCallback) {
24205
24452
  if (actionMatchers.length > 0) {
24206
24453
  throw new Error('`builder.addCase` should only be called before calling `builder.addMatcher`');
24207
24454
  }
24455
+ // A default case must always be registered last, after every specific case
24208
24456
  if (defaultCaseReducer) {
24209
24457
  throw new Error('`builder.addCase` should only be called before calling `builder.addDefaultCase`');
24210
24458
  }
24211
24459
  }
24212
24460
  const type = typeof typeOrActionCreator === 'string' ? typeOrActionCreator : typeOrActionCreator.type;
24461
+ // Each action type may only be handled by a single case reducer
24213
24462
  if (type in actionsMap) {
24214
24463
  throw new Error('addCase cannot be called with two reducers for the same action type');
24215
24464
  }
@@ -24217,7 +24466,9 @@ function executeReducerBuilderCallback(builderCallback) {
24217
24466
  return builder;
24218
24467
  },
24219
24468
  addMatcher(matcher, reducer) {
24469
+ // Skip this dev-only ordering check in production builds to avoid the extra overhead
24220
24470
  if (process.env.NODE_ENV !== 'production') {
24471
+ // A default case must always be registered last, after every matcher
24221
24472
  if (defaultCaseReducer) {
24222
24473
  throw new Error('`builder.addMatcher` should only be called before calling `builder.addDefaultCase`');
24223
24474
  }
@@ -24226,7 +24477,9 @@ function executeReducerBuilderCallback(builderCallback) {
24226
24477
  return builder;
24227
24478
  },
24228
24479
  addDefaultCase(reducer) {
24480
+ // Skip this dev-only uniqueness check in production builds to avoid the extra overhead
24229
24481
  if (process.env.NODE_ENV !== 'production') {
24482
+ // Only one default case reducer may be registered per builder
24230
24483
  if (defaultCaseReducer) {
24231
24484
  throw new Error('`builder.addDefaultCase` can only be called once');
24232
24485
  }
@@ -24243,8 +24496,10 @@ function executeReducerBuilderCallback(builderCallback) {
24243
24496
  ];
24244
24497
  }
24245
24498
 
24499
+ /** @inheritdoc */
24500
+ function isObservableInput(
24246
24501
  // biome-ignore lint/suspicious/noExplicitAny: Should allow any input type
24247
- function isObservableInput(input) {
24502
+ input) {
24248
24503
  // Check for null or undefined
24249
24504
  if (input === null || input === undefined) {
24250
24505
  return false;
@@ -24314,7 +24569,6 @@ function isObservableInput(input) {
24314
24569
  * // Primitive
24315
24570
  * toObservable(42).subscribe(x => console.log(x)); // 42
24316
24571
  */
24317
- // biome-ignore lint/suspicious/noExplicitAny: must be any to allow any type of input
24318
24572
  function toObservable(input, ...args) {
24319
24573
  // If input is already an ObservableInput (Observable, Promise, Iterable, etc), use RxJS 'from' to convert it
24320
24574
  if (isObservableInput(input)) {
@@ -24492,6 +24746,7 @@ var deepmerge = /*@__PURE__*/getDefaultExportFromCjs(deepmergeExports);
24492
24746
  * @returns The merged `MetaData` object, or `undefined` if both inputs are undefined.
24493
24747
  */
24494
24748
  const mergeMetadata = (source, target) => {
24749
+ // Nothing to merge when neither side has metadata
24495
24750
  if (!source && !target) {
24496
24751
  return undefined;
24497
24752
  }
@@ -24514,15 +24769,21 @@ const mergeMetadata = (source, target) => {
24514
24769
  *
24515
24770
  * @internal
24516
24771
  */
24772
+ // Deliberately co-located with `mergeMetadata` above
24773
+ // fusion-lint-disable-next-line single-export-per-file
24517
24774
  const mergeTelemetryItem = (target, source) => {
24775
+ // Both arguments are required to produce a valid merged telemetry item
24518
24776
  if (!target || !source) {
24519
24777
  throw new Error('Both target and source must be defined for merging telemetry items.');
24520
24778
  }
24779
+ // A type mismatch would produce an inconsistent telemetry item, so fail fast
24521
24780
  if (target.type && source.type && target.type !== source.type) {
24522
24781
  throw new Error('Mismatched telemetry item types.');
24523
24782
  }
24783
+ // Combine both scopes and de-duplicate via a Set before flattening back to an array
24524
24784
  const scope = [...new Set([...(target.scope ?? []), ...(source.scope ?? [])])];
24525
24785
  const metadata = mergeMetadata(target.metadata, source.metadata);
24786
+ // Source properties take precedence over target, with scope/metadata merged separately above
24526
24787
  return { ...target, ...source, scope, metadata };
24527
24788
  };
24528
24789
 
@@ -24553,11 +24814,18 @@ const mergeTelemetryItem = (target, source) => {
24553
24814
  class TelemetryConfigurator extends BaseConfigBuilder {
24554
24815
  #adaptersCallbacks = {};
24555
24816
  #metadata = [];
24817
+ /**
24818
+ * Creates a new `TelemetryConfigurator` and wires up the async adapter
24819
+ * resolution and metadata merging pipelines used by `createConfigAsync`.
24820
+ */
24556
24821
  constructor() {
24557
24822
  super();
24558
24823
  // Configure async adapter resolution using mergeMap to handle Promise/Observable adapter factories
24559
24824
  this._set('adapters', (args) => {
24560
- 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]) => {
24561
24829
  acc[identifier] = adapter;
24562
24830
  return acc;
24563
24831
  }, {}), defaultIfEmpty({}), shareReplay({ bufferSize: 1, refCount: true }));
@@ -24565,12 +24833,15 @@ class TelemetryConfigurator extends BaseConfigBuilder {
24565
24833
  // Configure metadata merging - handles multiple sync/async metadata sources
24566
24834
  this._set('metadata', async () => {
24567
24835
  const metadataItems = this.#metadata;
24568
- 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 }));
24569
24839
  });
24570
24840
  }
24571
24841
  /**
24572
24842
  * Registers a telemetry adapter with the configurator.
24573
24843
  *
24844
+ * @param identifier - The key used to register and later resolve this adapter.
24574
24845
  * @param adapter - The telemetry adapter to be added. The adapter's identifier is used as the key.
24575
24846
  * @returns The current instance of the configurator for method chaining.
24576
24847
  */
@@ -24580,7 +24851,8 @@ class TelemetryConfigurator extends BaseConfigBuilder {
24580
24851
  /**
24581
24852
  * Configures a telemetry adapter with the configurator.
24582
24853
  *
24583
- * @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
24584
24856
  * @returns The current instance for method chaining
24585
24857
  */
24586
24858
  configureAdapter(identifier, adapterFn) {
@@ -24645,7 +24917,7 @@ class TelemetryConfigurator extends BaseConfigBuilder {
24645
24917
  }
24646
24918
 
24647
24919
  // Generated by genversion.
24648
- const version$5 = '7.0.0';
24920
+ const version$5 = '7.0.1';
24649
24921
 
24650
24922
  /**
24651
24923
  * Enum representing the severity levels of telemetry items.
@@ -24739,6 +25011,8 @@ const TelemetryItemSchema = object({
24739
25011
  *
24740
25012
  * Extends TelemetryItemSchema and sets type to 'event'.
24741
25013
  */
25014
+ // Deliberately co-located with the other telemetry item schemas below
25015
+ // fusion-lint-disable-next-line single-export-per-file
24742
25016
  const TelemetryEventSchema = TelemetryItemSchema.extend({
24743
25017
  type: literal(TelemetryType.Event),
24744
25018
  });
@@ -24748,6 +25022,8 @@ const TelemetryEventSchema = TelemetryItemSchema.extend({
24748
25022
  * Extends TelemetryItemSchema and sets type to 'exception'.
24749
25023
  * Adds an exception property of type Error.
24750
25024
  */
25025
+ // Deliberately co-located with the other telemetry item schemas above/below
25026
+ // fusion-lint-disable-next-line single-export-per-file
24751
25027
  const TelemetryExceptionSchema = TelemetryItemSchema.extend({
24752
25028
  type: literal(TelemetryType.Exception),
24753
25029
  exception: _instanceof(Error).describe('The exception object.'),
@@ -24758,6 +25034,8 @@ const TelemetryExceptionSchema = TelemetryItemSchema.extend({
24758
25034
  * Extends TelemetryItemSchema and sets type to 'metric'.
24759
25035
  * Adds a value property for the metric value.
24760
25036
  */
25037
+ // Deliberately co-located with the other telemetry item schemas above/below
25038
+ // fusion-lint-disable-next-line single-export-per-file
24761
25039
  const TelemetryMetricSchema = TelemetryItemSchema.extend({
24762
25040
  type: literal(TelemetryType.Metric),
24763
25041
  value: number$1().describe('The value of the metric.'),
@@ -24767,6 +25045,8 @@ const TelemetryMetricSchema = TelemetryItemSchema.extend({
24767
25045
  *
24768
25046
  * Extends TelemetryEventSchema and allows passthrough of additional properties.
24769
25047
  */
25048
+ // Deliberately co-located with the other telemetry item schemas above
25049
+ // fusion-lint-disable-next-line single-export-per-file
24770
25050
  const TelemetryCustomEventSchema = TelemetryEventSchema.extend({
24771
25051
  type: literal(TelemetryType.Custom),
24772
25052
  }).passthrough();
@@ -24788,7 +25068,6 @@ const TelemetryCustomEventSchema = TelemetryEventSchema.extend({
24788
25068
  * @template TInit The type of the event details.
24789
25069
  * @template TType The type of the event type.
24790
25070
  */
24791
- // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: no other way to define a class with multiple signatures
24792
25071
  class FrameworkEvent {
24793
25072
  __type;
24794
25073
  #detail;
@@ -24867,6 +25146,7 @@ class FrameworkEvent {
24867
25146
  * If the event is cancelable, this method sets the `canceled` property to `true`.
24868
25147
  */
24869
25148
  preventDefault() {
25149
+ // Only cancelable events can be canceled
24870
25150
  if (this.cancelable) {
24871
25151
  this.#canceled = true;
24872
25152
  }
@@ -24881,44 +25161,54 @@ class FrameworkEvent {
24881
25161
  }
24882
25162
 
24883
25163
  /**
24884
- * Represents a telemetry event within the framework.
25164
+ * Event representing an error that occurred within a telemetry provider.
24885
25165
  *
24886
- * This event encapsulates a `TelemetryItem` and is associated with a specific `ITelemetryProvider` source.
24887
- * 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.
24888
25169
  *
24889
25170
  * @example
24890
25171
  * ```typescript
24891
- * const event = new TelemetryEvent(item, provider);
25172
+ * const errorEvent = new TelemetryErrorEvent(new Error('Something went wrong'), telemetryProvider);
24892
25173
  * ```
24893
25174
  *
24894
- * @param item - The telemetry item containing event data.
24895
- * @param source - The telemetry provider that is the source of this event.
25175
+ * @extends FrameworkEvent
24896
25176
  */
24897
- class TelemetryEvent extends FrameworkEvent {
24898
- constructor(item, source) {
24899
- 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 });
24900
25186
  }
24901
25187
  }
25188
+
24902
25189
  /**
24903
- * Event representing an error that occurred within a telemetry provider.
25190
+ * Represents a telemetry event within the framework.
24904
25191
  *
24905
- * @remarks
24906
- * This event is emitted when an error is encountered by an {@link ITelemetryProvider}.
24907
- * 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.
24908
25194
  *
24909
25195
  * @example
24910
25196
  * ```typescript
24911
- * const errorEvent = new TelemetryErrorEvent(new Error('Something went wrong'), telemetryProvider);
25197
+ * const event = new TelemetryEvent(item, provider);
24912
25198
  * ```
24913
25199
  *
24914
- * @extends FrameworkEvent
24915
- *
24916
- * @param error - The error instance that was thrown.
24917
- * @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.
24918
25202
  */
24919
- class TelemetryErrorEvent extends FrameworkEvent {
24920
- constructor(error, source) {
24921
- 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 });
24922
25212
  }
24923
25213
  }
24924
25214
 
@@ -24966,6 +25256,7 @@ class Measurement {
24966
25256
  */
24967
25257
  clone(resetOptions = {}) {
24968
25258
  const clonedMeasurement = new Measurement(this.#provider, this.#data);
25259
+ // Preserve the original start time when requested, otherwise the clone starts fresh
24969
25260
  if (resetOptions.preserveStartTime) {
24970
25261
  clonedMeasurement.#startTime = this.#startTime;
24971
25262
  }
@@ -24980,6 +25271,7 @@ class Measurement {
24980
25271
  * Resets the measured flag and, unless `preserveStartTime` is true, updates the start time to the current performance time.
24981
25272
  */
24982
25273
  reset(options) {
25274
+ // Keep the existing start time when preserving, otherwise restart the clock
24983
25275
  if (!options?.preserveStartTime) {
24984
25276
  this.#startTime = performance.now();
24985
25277
  }
@@ -24994,6 +25286,7 @@ class Measurement {
24994
25286
  * @param options - Optional measurement options.
24995
25287
  * @param options.markAsMeasured - If true, marks this measurement as completed.
24996
25288
  * @param options.resetStartTime - If true, resets the start time after measuring.
25289
+ * @returns The elapsed duration in milliseconds since the last start time.
24997
25290
  */
24998
25291
  measure(data, options) {
24999
25292
  const measuredData = mergeTelemetryItem(this.#data, data ?? {});
@@ -25002,9 +25295,11 @@ class Measurement {
25002
25295
  ...measuredData,
25003
25296
  value: duration,
25004
25297
  });
25298
+ // Mark as measured only when explicitly requested by the caller
25005
25299
  if (options?.markAsMeasured) {
25006
25300
  this.#measured = true;
25007
25301
  }
25302
+ // Restart the clock only when explicitly requested by the caller
25008
25303
  if (options?.resetStartTime) {
25009
25304
  this.#startTime = performance.now();
25010
25305
  }
@@ -25013,7 +25308,7 @@ class Measurement {
25013
25308
  /**
25014
25309
  * Resolves a given promise, measures the result using the provided options, and returns the resolved value.
25015
25310
  *
25016
- * @typeParam T - The type of the resolved value from the promise.
25311
+ * @template T - The type of the resolved value from the promise.
25017
25312
  * @param promise - The promise to resolve.
25018
25313
  * @param options - Optional configuration for resolving and measuring:
25019
25314
  * - `data`: A value or a function that receives the resolved result and returns measurement data.
@@ -25031,7 +25326,7 @@ class Measurement {
25031
25326
  * The function `fn` can return either a value of type `T` or a Promise of type `T`.
25032
25327
  * Optionally, resolution options can be provided.
25033
25328
  *
25034
- * @typeParam T - The return type of the function to execute.
25329
+ * @template T - The return type of the function to execute.
25035
25330
  * @param fn - A function that returns a value or a Promise to be resolved.
25036
25331
  * @param options - Optional resolution options to customize the resolve behavior.
25037
25332
  * @returns A Promise that resolves to the result of the executed function.
@@ -25049,6 +25344,7 @@ class Measurement {
25049
25344
  * @see https://github.com/tc39/proposal-explicit-resource-management
25050
25345
  */
25051
25346
  [Symbol.dispose]() {
25347
+ // Ensure a measurement is always taken before the instance is discarded
25052
25348
  if (!this.#measured) {
25053
25349
  try {
25054
25350
  this.measure();
@@ -25091,7 +25387,10 @@ const resolveMetadata = (metadata, args) => {
25091
25387
  * @param args - The arguments required by the metadata extractor, including the telemetry item.
25092
25388
  * @returns An Observable emitting the telemetry item with applied metadata, or an error telemetry item if resolution fails.
25093
25389
  */
25390
+ // Deliberately co-located with `resolveMetadata` above
25391
+ // fusion-lint-disable-next-line single-export-per-file
25094
25392
  const applyMetadata = (metadata, args) => {
25393
+ // Resolve metadata, merge it into the item, and fall back to an error item on failure
25095
25394
  return resolveMetadata(metadata, args).pipe(defaultIfEmpty({}), // Ensure we always have an object to merge with
25096
25395
  first(), // Take the first emitted value from the metadata extractor
25097
25396
  // Merge the resolved metadata with the telemetry item.
@@ -25145,20 +25444,39 @@ class TelemetryProvider extends BaseModuleProvider {
25145
25444
  #filters;
25146
25445
  #defaultScope;
25147
25446
  #eventProvider;
25447
+ /**
25448
+ * Stream of every telemetry item tracked by this provider.
25449
+ *
25450
+ * @returns An observable emitting each tracked `TelemetryItem`.
25451
+ */
25148
25452
  get items() {
25149
25453
  return this.#items.asObservable();
25150
25454
  }
25151
25455
  /**
25152
25456
  * Returns true if the provider has been initialized.
25457
+ *
25458
+ * @returns `true` when {@link initialize} has completed, otherwise `false`.
25153
25459
  */
25154
25460
  get initialized() {
25155
25461
  return this.#initialized;
25156
25462
  }
25157
25463
  #metadata;
25158
25464
  #modules;
25465
+ /**
25466
+ * Sets the resolved framework module instances used to resolve metadata.
25467
+ *
25468
+ * @param value - The resolved framework module instances.
25469
+ */
25159
25470
  set modules(value) {
25160
25471
  this.#modules = value;
25161
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
+ */
25162
25480
  constructor(config, deps) {
25163
25481
  super({ version: version$5, config });
25164
25482
  this.#adapters = config?.adapters ?? {};
@@ -25218,11 +25536,13 @@ class TelemetryProvider extends BaseModuleProvider {
25218
25536
  async _initializeAdapters() {
25219
25537
  // Initialize all adapters, do it in parallel
25220
25538
  const adapterEntries = Object.entries(this.#adapters);
25539
+ // Kick off every adapter's initialize() concurrently
25221
25540
  const initializationPromises = adapterEntries.map(([_identifier, adapter]) => adapter.initialize());
25222
25541
  // Wait for all adapters to settle (either resolve or reject)
25223
25542
  const results = await Promise.allSettled(initializationPromises);
25224
25543
  // Check each result and dispatch errors for failed initializations
25225
25544
  for (const [index, result] of results.entries()) {
25545
+ // Only rejected adapters need an error dispatched; fulfilled ones need no action
25226
25546
  if (result.status === 'rejected') {
25227
25547
  const [identifier] = adapterEntries[index];
25228
25548
  this._dispatchError(new Error(`Failed to initialize telemetry adapter "${identifier}"`, {
@@ -25239,8 +25559,10 @@ class TelemetryProvider extends BaseModuleProvider {
25239
25559
  *
25240
25560
  * @returns Subscription to the telemetry item stream
25241
25561
  * @protected
25562
+ * @throws {Error} When the provider has not been initialized yet.
25242
25563
  */
25243
25564
  _connectAdapters() {
25565
+ // Adapters can only be connected after the provider has completed initialization
25244
25566
  if (!this.#initialized) {
25245
25567
  throw new Error('TelemetryProvider is not initialized');
25246
25568
  }
@@ -25293,6 +25615,7 @@ class TelemetryProvider extends BaseModuleProvider {
25293
25615
  * @protected
25294
25616
  */
25295
25617
  _dispatchEntityEvent(item) {
25618
+ // Only dispatch when an event provider is configured
25296
25619
  if (this.#eventProvider) {
25297
25620
  // Wrap the telemetry item in a TelemetryEvent and dispatch it
25298
25621
  this.#eventProvider.dispatchEvent(new TelemetryEvent(item, this));
@@ -25305,6 +25628,7 @@ class TelemetryProvider extends BaseModuleProvider {
25305
25628
  * @protected
25306
25629
  */
25307
25630
  _dispatchError(error) {
25631
+ // Only dispatch when an event provider is configured
25308
25632
  if (this.#eventProvider) {
25309
25633
  this.#eventProvider.dispatchEvent(new TelemetryErrorEvent(error, this));
25310
25634
  }
@@ -25322,6 +25646,7 @@ class TelemetryProvider extends BaseModuleProvider {
25322
25646
  * });
25323
25647
  */
25324
25648
  track(item) {
25649
+ // Dispatch to the type-specific tracker so each item is validated with the right schema
25325
25650
  switch (item.type) {
25326
25651
  case TelemetryType.Event:
25327
25652
  this.trackEvent(item);
@@ -25434,6 +25759,7 @@ class TelemetryProvider extends BaseModuleProvider {
25434
25759
  */
25435
25760
  getAdapter(identifier) {
25436
25761
  const adapter = this.#adapters[identifier];
25762
+ // Report a missing adapter as a telemetry exception rather than throwing
25437
25763
  if (!adapter) {
25438
25764
  this.trackException({
25439
25765
  name: 'TelemetryAdapterNotFound',
@@ -25501,6 +25827,7 @@ const module$3 = {
25501
25827
  const mapConfiguratorEvents = (
25502
25828
  // biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
25503
25829
  configurator) => {
25830
+ // Map each configurator event into a standardized telemetry item shape
25504
25831
  return configurator.event$.pipe(map$1((event) => {
25505
25832
  const item = {
25506
25833
  name: event.name,
@@ -25512,6 +25839,7 @@ configurator) => {
25512
25839
  },
25513
25840
  scope: ['configuration'],
25514
25841
  };
25842
+ // An event carrying an error is reported as a telemetry exception
25515
25843
  if (event.error) {
25516
25844
  return {
25517
25845
  ...item,
@@ -25519,6 +25847,7 @@ configurator) => {
25519
25847
  exception: event.error,
25520
25848
  };
25521
25849
  }
25850
+ // An event carrying a metric value is reported as a telemetry metric
25522
25851
  if (event.metric) {
25523
25852
  return {
25524
25853
  ...item,
@@ -25568,9 +25897,11 @@ configurator, options) => {
25568
25897
  configurator.addConfig({
25569
25898
  module: module$3,
25570
25899
  configure: async (builder, ref) => {
25900
+ // Attach configurator events to the telemetry builder only when opted in
25571
25901
  {
25572
25902
  builder.attachItems(mapConfiguratorEvents(configurator));
25573
25903
  }
25904
+ // Run the caller-supplied configure callback when one was provided
25574
25905
  if (options?.configure) {
25575
25906
  await Promise.resolve(options.configure(builder, ref));
25576
25907
  }
@@ -42517,11 +42848,14 @@ class MsalClient extends PublicClientApplication {
42517
42848
  * - **Redirect**: Navigates browser to Microsoft login page. Returns `void` because the browser
42518
42849
  * navigates to a new page. After redirect completes, the result will be available via
42519
42850
  * `handleRedirectPromise()` when the app loads on the new page.
42851
+ *
42852
+ * @throws {Error} If an invalid `options.behavior` value is provided.
42520
42853
  */
42521
42854
  async login(options) {
42522
42855
  // Attempt silent authentication first if enabled
42523
42856
  // This provides better UX by avoiding unnecessary popups/redirects
42524
42857
  if (options.silent) {
42858
+ // Warn early when neither an account nor login hint is available for silent SSO
42525
42859
  if (!options.request.account && !options.request.loginHint) {
42526
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);
42527
42861
  }
@@ -42579,6 +42913,7 @@ class MsalClient extends PublicClientApplication {
42579
42913
  * ```
42580
42914
  */
42581
42915
  async logout(options) {
42916
+ // Warn when no account was supplied since the active account will be used instead
42582
42917
  if (!options?.account) {
42583
42918
  this.getLogger().warning('No account available for logout, please provide an account in the options', FUSION_CORRELATION_ID);
42584
42919
  }
@@ -42610,18 +42945,23 @@ class MsalClient extends PublicClientApplication {
42610
42945
  *
42611
42946
  * The default silent behavior is determined by presence of account in the request.
42612
42947
  * This provides optimal UX by minimizing unnecessary user interactions.
42948
+ *
42949
+ * @throws {Error} If no `request` is provided in `options`.
42613
42950
  */
42614
42951
  async acquireToken(options) {
42615
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
42616
42954
  if (!request) {
42617
42955
  throw new Error('No request provided, please provide a request in the options');
42618
42956
  }
42957
+ // Warn when no scopes are requested, since MSAL requires at least one scope
42619
42958
  if (request.scopes.length === 0) {
42620
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);
42621
42960
  }
42622
42961
  // Attempt silent token acquisition first
42623
42962
  // This fetches from cache or uses refresh token without user interaction
42624
42963
  if (silent) {
42964
+ // Only silent-acquire when an account is available to look up cached tokens for
42625
42965
  if (request.account) {
42626
42966
  try {
42627
42967
  this.getLogger().verbose('Attempting to acquire token silently', request.correlationId || FUSION_CORRELATION_ID);
@@ -42681,6 +43021,7 @@ const levelMap = {
42681
43021
  const parseMsalMessage = (message) => {
42682
43022
  // Match the structured format: [timestamp] : [correlationId] : [package] : [level] - [component] - [message]
42683
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
42684
43025
  if (match) {
42685
43026
  const [, _timestamp, _correlationId, packageInfo, _logLevel, component, logMessage] = match;
42686
43027
  return {
@@ -42743,7 +43084,7 @@ const createClientLogCallback = (provider, metadata, scope) => {
42743
43084
  };
42744
43085
 
42745
43086
  // Generated by genversion.
42746
- const version$2 = '10.0.1';
43087
+ const version$2 = '10.0.2';
42747
43088
 
42748
43089
  /**
42749
43090
  * Zod schema for telemetry configuration validation.
@@ -42796,6 +43137,7 @@ class MsalConfigurator extends BaseConfigBuilder {
42796
43137
  * The MSAL module version being configured.
42797
43138
  *
42798
43139
  * @default Latest
43140
+ * @returns The configured MSAL module version.
42799
43141
  */
42800
43142
  get version() {
42801
43143
  return version$2;
@@ -42811,6 +43153,7 @@ class MsalConfigurator extends BaseConfigBuilder {
42811
43153
  this._set('version', async () => this.version);
42812
43154
  // Auto-detect and integrate telemetry module if available
42813
43155
  this._set('telemetry.provider', async (args) => {
43156
+ // Only resolve the telemetry instance when the telemetry module is registered
42814
43157
  if (args.hasModule('telemetry')) {
42815
43158
  const telemetry = await args.requireInstance('telemetry');
42816
43159
  return telemetry;
@@ -43034,7 +43377,7 @@ class MsalConfigurator extends BaseConfigBuilder {
43034
43377
  const config = await MsalConfigSchema.parseAsync(rawConfig);
43035
43378
  // Auto-create client if config provided but no client instance
43036
43379
  // This allows users to provide configuration without manually instantiating the client
43037
- if (!config.client && !!this.#msalConfig) {
43380
+ if (!config.client && this.#msalConfig) {
43038
43381
  const clientConfig = this.#msalConfig;
43039
43382
  config.telemetry.provider?.trackEvent({
43040
43383
  name: 'module-msal.configurator._processConfig.creating-client',
@@ -43203,12 +43546,15 @@ class VersionError extends Error {
43203
43546
  function mapVersionToEnumVersion(version) {
43204
43547
  console.log('Resolving version:', version);
43205
43548
  const coercedVersion = semver.coerce(version);
43549
+ // An uncoercible version string cannot be mapped to a module version
43206
43550
  if (!coercedVersion) {
43207
43551
  throw new Error(`Invalid version: ${version}`);
43208
43552
  }
43553
+ // Versions before 4.0.0 (including 2.x) map to the v2-compatible module version
43209
43554
  if (semver.satisfies(coercedVersion, '<4.0.0')) {
43210
43555
  return MsalModuleVersion.V2;
43211
43556
  }
43557
+ // Versions before 7.0.0 (4.x and 5.x/6.x) map to the v4-compatible module version
43212
43558
  if (semver.satisfies(coercedVersion, '<7.0.0')) {
43213
43559
  return MsalModuleVersion.V4;
43214
43560
  }
@@ -43271,6 +43617,7 @@ function resolveVersion(version) {
43271
43617
  warnings.push(missingVersionWarning.message);
43272
43618
  wantedVersion = latestVersion;
43273
43619
  }
43620
+ // Warn when the requested major version trails the latest major version
43274
43621
  if (wantedVersion.major < latestVersion.major) {
43275
43622
  const majorBehindVersionWarning = new VersionError(`Requested major version ${wantedVersion.major} is behind the latest major version ${latestVersion.major}`, wantedVersion, latestVersion);
43276
43623
  warnings.push(majorBehindVersionWarning.message);
@@ -43365,9 +43712,11 @@ function mapAuthenticationResult(result) {
43365
43712
  function createProxyClient(client) {
43366
43713
  const proxy = new Proxy(client, {
43367
43714
  get: (target, prop) => {
43715
+ // Adapt each v4 client member to the v2-compatible shape expected by callers
43368
43716
  switch (prop) {
43369
43717
  case 'getAllAccounts': {
43370
43718
  return () => {
43719
+ // Map each v4 account shape to its v2-compatible equivalent
43371
43720
  return target.getAllAccounts().map(mapAccountInfo);
43372
43721
  };
43373
43722
  }
@@ -43399,6 +43748,7 @@ function createProxyClient(client) {
43399
43748
  case 'handleRedirectPromise': {
43400
43749
  return async () => {
43401
43750
  const result = await target.handleRedirectPromise();
43751
+ // No redirect result means there's nothing pending to map
43402
43752
  if (!result) {
43403
43753
  return null;
43404
43754
  }
@@ -43408,6 +43758,7 @@ function createProxyClient(client) {
43408
43758
  case 'getActiveAccount': {
43409
43759
  return () => {
43410
43760
  const account = target.getActiveAccount();
43761
+ // No active account means there's nothing to map
43411
43762
  if (!account) {
43412
43763
  return null;
43413
43764
  }
@@ -43494,6 +43845,8 @@ function createProxyClient(client) {
43494
43845
  }
43495
43846
  },
43496
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.
43497
43850
  return proxy;
43498
43851
  }
43499
43852
 
@@ -43504,6 +43857,7 @@ function createProxyClient(client) {
43504
43857
  * @returns True if the request is in v4 format (has a `request` property with `scopes` and `account`)
43505
43858
  */
43506
43859
  function isRequestV4(req) {
43860
+ // Non-object/null values can never match the v4 request shape
43507
43861
  if (typeof req !== 'object' || req === null) {
43508
43862
  return false;
43509
43863
  }
@@ -43537,6 +43891,7 @@ function createProxyProvider$2(provider) {
43537
43891
  // Use Proxy to intercept property access and provide v2-compatible implementations
43538
43892
  const proxy = new Proxy(provider, {
43539
43893
  get: (target, prop) => {
43894
+ // Adapt each v4 provider member to the v2-compatible shape expected by callers
43540
43895
  switch (prop) {
43541
43896
  case 'version': {
43542
43897
  return provider.version;
@@ -43551,6 +43906,7 @@ function createProxyProvider$2(provider) {
43551
43906
  case 'defaultClient': {
43552
43907
  // Deprecated property - redirect to client with warning
43553
43908
  console.warn('defaultClient is deprecated, use client instead');
43909
+ // Same v2-compatible client wrapper as the `client` case above.
43554
43910
  return v2Client;
43555
43911
  }
43556
43912
  case 'defaultAccount': {
@@ -43634,6 +43990,7 @@ function createProxyProvider$2(provider) {
43634
43990
  };
43635
43991
  }
43636
43992
  default: {
43993
+ // The proxy is awaited in some contexts, so `then` must resolve to undefined
43637
43994
  if (prop === 'then') {
43638
43995
  return undefined;
43639
43996
  }
@@ -43671,6 +44028,7 @@ function createProxyProvider$1(provider) {
43671
44028
  // Create passthrough proxy that only overrides msalVersion
43672
44029
  return new Proxy(provider, {
43673
44030
  get: (target, prop) => {
44031
+ // Passthrough every property except msalVersion, which reports V4 compatibility
43674
44032
  switch (prop) {
43675
44033
  case 'msalVersion': {
43676
44034
  // Report as V4 for compatibility tracking
@@ -43732,6 +44090,9 @@ function createProxyProvider$1(provider) {
43732
44090
  * @param version - The target version string (e.g., '2.0.0', '4.0.0')
43733
44091
  * @returns A proxy provider compatible with the specified version
43734
44092
  *
44093
+ * @template T - The provider interface type expected by the target version.
44094
+ * @throws {Error} If the resolved version is not supported.
44095
+ *
43735
44096
  * @example
43736
44097
  * ```typescript
43737
44098
  * const baseProvider = new MsalProvider(config);
@@ -43741,6 +44102,7 @@ function createProxyProvider$1(provider) {
43741
44102
  function createProxyProvider(provider, version) {
43742
44103
  // Resolve the requested version to determine which proxy to create
43743
44104
  const { enumVersion } = resolveVersion(version);
44105
+ // Build the version-appropriate proxy based on the resolved MSAL module version
43744
44106
  switch (enumVersion) {
43745
44107
  case MsalModuleVersion.V2:
43746
44108
  // Create v2-compatible proxy with legacy API adapters
@@ -43752,6 +44114,7 @@ function createProxyProvider(provider, version) {
43752
44114
  // Create transparent proxy for v5 - passes through to original provider
43753
44115
  return new Proxy(provider, {
43754
44116
  get: (target, prop) => {
44117
+ // Delegate every known property/method to the underlying provider
43755
44118
  switch (prop) {
43756
44119
  case 'version': {
43757
44120
  return target.version;
@@ -43838,6 +44201,8 @@ class MsalProvider extends BaseModuleProvider {
43838
44201
  * Default OAuth scopes used when the caller provides no scopes.
43839
44202
  *
43840
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.
43841
44206
  */
43842
44207
  get defaultScopes() {
43843
44208
  const clientId = this.#client.clientId;
@@ -43860,6 +44225,8 @@ class MsalProvider extends BaseModuleProvider {
43860
44225
  *
43861
44226
  * Provides access to the underlying MSAL PublicClientApplication for advanced use cases.
43862
44227
  * Prefer using provider methods for standard authentication operations.
44228
+ *
44229
+ * @returns The underlying MSAL client instance.
43863
44230
  */
43864
44231
  get client() {
43865
44232
  return this.#client;
@@ -43869,6 +44236,8 @@ class MsalProvider extends BaseModuleProvider {
43869
44236
  *
43870
44237
  * Returns the active account if a user is authenticated, or null if no user is logged in.
43871
44238
  * This is a shorthand for `client.getActiveAccount()`.
44239
+ *
44240
+ * @returns The currently authenticated account, or `null` if no user is logged in.
43872
44241
  */
43873
44242
  get account() {
43874
44243
  return this.#client.getActiveAccount();
@@ -43932,6 +44301,8 @@ class MsalProvider extends BaseModuleProvider {
43932
44301
  *
43933
44302
  * The provider will attempt automatic login with empty scopes if requiresAuth is true.
43934
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.
43935
44306
  */
43936
44307
  async initialize() {
43937
44308
  // Guard: skip authentication when running inside MSAL's hidden iframe.
@@ -43958,6 +44329,7 @@ class MsalProvider extends BaseModuleProvider {
43958
44329
  // Use MSAL's acquireTokenByCode to exchange backend auth code for tokens
43959
44330
  // This follows Microsoft's standard SPA Auth Code Flow pattern
43960
44331
  const clientId = this.#client.clientId;
44332
+ // A client ID is required to build the default scope used for the auth code exchange
43961
44333
  if (!clientId) {
43962
44334
  throw new Error('Client ID is required for auth code exchange');
43963
44335
  }
@@ -44001,6 +44373,7 @@ class MsalProvider extends BaseModuleProvider {
44001
44373
  // Priority 1: Check if returning from redirect-based authentication
44002
44374
  // This handles cases where user just completed a login/acquireToken via redirect
44003
44375
  const handleRedirectResult = await this.handleRedirect();
44376
+ // A returned account means the user just completed a redirect-based login
44004
44377
  if (handleRedirectResult?.account) {
44005
44378
  // Successfully authenticated via redirect - set as active account
44006
44379
  // This means the user was redirected to Microsoft and came back authenticated
@@ -44017,6 +44390,7 @@ class MsalProvider extends BaseModuleProvider {
44017
44390
  // Note: Using default scopes here as we don't know what scopes the app needs yet
44018
44391
  // App should call acquireToken with actual scopes after initialization
44019
44392
  const loginResult = await this.login({ request: { scopes: this.defaultScopes } });
44393
+ // Only set the active account when the automatic login actually returned one
44020
44394
  if (loginResult?.account) {
44021
44395
  // Automatic login successful - set as active account
44022
44396
  this.#client.setActiveAccount(loginResult.account);
@@ -44065,6 +44439,8 @@ class MsalProvider extends BaseModuleProvider {
44065
44439
  * @remark Empty scopes are currently tracked as telemetry exceptions but execution continues for monitoring purposes.
44066
44440
  * This behavior will be changed to throw exceptions once sufficient metrics are collected.
44067
44441
  *
44442
+ * @throws {Error} Re-throws any error encountered during token acquisition after tracking it via telemetry.
44443
+ *
44068
44444
  * @example
44069
44445
  * ```typescript
44070
44446
  * // Modern API format
@@ -44107,6 +44483,7 @@ class MsalProvider extends BaseModuleProvider {
44107
44483
  }
44108
44484
  // Handle empty scopes - currently monitoring for telemetry, will throw in future
44109
44485
  if (candidateScopes.length === 0) {
44486
+ // Fall back to the client-id-derived default scope when one is available
44110
44487
  if (defaultScopes.length > 0) {
44111
44488
  this._trackEvent('acquireToken.missing-scope.defaulted', TelemetryLevel.Warning, {
44112
44489
  properties: { ...telemetryProperties, defaultScopes },
@@ -44118,7 +44495,7 @@ class MsalProvider extends BaseModuleProvider {
44118
44495
  exception,
44119
44496
  properties: telemetryProperties,
44120
44497
  });
44121
- // TODO: throw exception when sufficient metrics are collected
44498
+ // TODO(#5113): throw exception when sufficient metrics are collected
44122
44499
  // This allows us to monitor how often empty scopes are provided before enforcing validation
44123
44500
  }
44124
44501
  }
@@ -44245,6 +44622,7 @@ class MsalProvider extends BaseModuleProvider {
44245
44622
  // Fall through to interactive authentication
44246
44623
  }
44247
44624
  }
44625
+ // Perform interactive authentication based on specified behavior
44248
44626
  switch (behavior) {
44249
44627
  case 'popup':
44250
44628
  return await this.#client.loginPopup(request);
@@ -44326,6 +44704,7 @@ class MsalProvider extends BaseModuleProvider {
44326
44704
  async handleRedirect() {
44327
44705
  // Process any pending redirect from authentication flow
44328
44706
  const result = await this.client.handleRedirectPromise();
44707
+ // Only track/log when a redirect result is actually returned
44329
44708
  if (result) {
44330
44709
  // Track successful redirect completion for monitoring
44331
44710
  this._trackEvent('handleRedirect.success', TelemetryLevel.Information, {
@@ -44351,6 +44730,9 @@ class MsalProvider extends BaseModuleProvider {
44351
44730
  * - Version compatibility is tracked via telemetry
44352
44731
  * - Throws error if unsupported version is requested
44353
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
+ *
44354
44736
  * @example
44355
44737
  * ```typescript
44356
44738
  * // Create v2-compatible proxy
@@ -44480,6 +44862,7 @@ const module$2 = {
44480
44862
  // Priority 2: Check if provider exists in parent module (proxy compatibility)
44481
44863
  // This allows child applications to reuse parent's authentication provider
44482
44864
  const hostProvider = init.ref?.auth;
44865
+ // Reuse the parent's provider (via a version-compatible proxy) when available
44483
44866
  if (hostProvider) {
44484
44867
  try {
44485
44868
  const proxyProvider = hostProvider.createProxyProvider(config.version);
@@ -44488,7 +44871,7 @@ const module$2 = {
44488
44871
  catch (error) {
44489
44872
  console.error('MsalModule::Failed to create proxy provider', error);
44490
44873
  // Fallback to host provider to prevent app breakage during migration
44491
- // 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
44492
44875
  return hostProvider;
44493
44876
  }
44494
44877
  }
@@ -44523,7 +44906,7 @@ const module$2 = {
44523
44906
  * ```
44524
44907
  */
44525
44908
  const enableMSAL = (
44526
- // @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
44527
44910
  configurator, configure) => {
44528
44911
  const config = configure ? configureMsal(configure) : { module: module$2 };
44529
44912
  configurator.addConfig(config);
@@ -44557,7 +44940,11 @@ const configureMsal = (configure) => ({
44557
44940
  * @returns A function that takes an Observable stream of `QueryQueueItem` and returns an Observable
44558
44941
  * stream where each item is processed in sequence by the provided callback.
44559
44942
  */
44560
- 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
+
44561
44948
  /**
44562
44949
  * Takes a function that transforms each item in a queue and returns an Observable.
44563
44950
  * It processes each item concurrently, potentially leading to out-of-order execution.
@@ -44568,7 +44955,10 @@ const concatQueue = (...args) => (source$) => source$.pipe(concatMap(...args));
44568
44955
  * @returns A function that takes an Observable stream of `QueryQueueItem` and returns an Observable
44569
44956
  * stream where each item is processed concurrently by the provided callback.
44570
44957
  */
44571
- 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
+
44572
44962
  /**
44573
44963
  * Takes a function that transforms each item in a queue and returns an Observable.
44574
44964
  * It processes each item by cancelling the current task if a new one arrives.
@@ -44580,7 +44970,10 @@ const mergeQueue = (...args) => (source$) => source$.pipe(mergeMap(...args));
44580
44970
  * stream where each item is processed by the provided callback, but only the latest
44581
44971
  * item's result is emitted.
44582
44972
  */
44583
- 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
+
44584
44977
  /**
44585
44978
  * Transforms a query result Observable into a plain value Observable by extracting the `value` property.
44586
44979
  *
@@ -44603,7 +44996,9 @@ const switchQueue = (...args) => (source$) => source$.pipe(switchMap(...args));
44603
44996
  * });
44604
44997
  * ```
44605
44998
  */
44606
- 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));
44607
45002
 
44608
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;
44609
45004
 
@@ -44812,6 +45207,12 @@ v5.URL = URL$1;
44812
45207
  class QueryClientError extends Error {
44813
45208
  type;
44814
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
+ */
44815
45216
  constructor(type, args) {
44816
45217
  super(args.message, { cause: args.cause });
44817
45218
  this.type = type;
@@ -44879,14 +45280,19 @@ const actions$1 = createActions$1();
44879
45280
  * @param action$ - The stream of actions being dispatched in the system.
44880
45281
  * @returns An Observable that emits execute actions for each request action.
44881
45282
  */
44882
- 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
+
44883
45287
  /**
44884
45288
  * Handles the execution of a query.
44885
45289
  *
44886
45290
  * @param fetch - The function used to execute the query.
44887
45291
  * @returns A flow that handles the execution of the query.
44888
45292
  */
44889
- 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]) => {
44890
45296
  const transaction = action.payload;
44891
45297
  const request = state[transaction];
44892
45298
  // Create an AbortController instance to manage cancellation.
@@ -44899,6 +45305,7 @@ const handleExecution = (fetch) => (action$, state$) => action$.pipe(filter(acti
44899
45305
  }
44900
45306
  }));
44901
45307
  try {
45308
+ // map the fetch outcome to a success/failure action, cancelling early if aborted
44902
45309
  return from(fetch(request.args, controller.signal)).pipe(map$1((value) => actions$1.execute.success({
44903
45310
  ...request,
44904
45311
  status: 'complete',
@@ -44913,6 +45320,7 @@ const handleExecution = (fetch) => (action$, state$) => action$.pipe(filter(acti
44913
45320
  return of(actions$1.execute.failure(err, transaction));
44914
45321
  }
44915
45322
  }));
45323
+
44916
45324
  /**
44917
45325
  * Handles execution failure by scheduling retries according to the provided retry options.
44918
45326
  * If the maximum number of retries is exceeded or no retry options are provided, it emits an error action.
@@ -44922,6 +45330,7 @@ const handleExecution = (fetch) => (action$, state$) => action$.pipe(filter(acti
44922
45330
  */
44923
45331
  const handleFailure = (config) => {
44924
45332
  return (action$, state$) => {
45333
+ // filter for execution failures and decide whether to retry or report an error
44925
45334
  return action$.pipe(
44926
45335
  // Filter for actions that indicate an execution failure.
44927
45336
  filter(actions$1.execute.failure.match),
@@ -44978,6 +45387,7 @@ const handleFailure = (config) => {
44978
45387
  filter(([_, state]) => !!state[transaction]),
44979
45388
  // If the request has been removed, throw an error.
44980
45389
  tap(([, state]) => {
45390
+ // Re-check inside the tap since the request could have been removed during the delay
44981
45391
  if (!state[transaction]) {
44982
45392
  throw new QueryClientError('error', {
44983
45393
  message: 'request not found, most likely removed while scheduled for retry',
@@ -45033,9 +45443,9 @@ const createReducer$1 = (initial = {}) => createReducer$2(initial, (builder) =>
45033
45443
  builder.addCase(actions$1.execute, (state, action) => {
45034
45444
  // Locates the query entry in the state using the transaction ID provided in `action.payload`.
45035
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.
45036
45448
  if (entry) {
45037
- // If the entry is found, logs the current timestamp to the `execution` array
45038
- // to indicate when the execution action was triggered.
45039
45449
  entry.execution.push(Date.now());
45040
45450
  // Updates the status of the entry to 'active' to reflect that the execution
45041
45451
  // process has started.
@@ -45050,9 +45460,9 @@ const createReducer$1 = (initial = {}) => createReducer$2(initial, (builder) =>
45050
45460
  builder.addCase(actions$1.execute.failure, (state, action) => {
45051
45461
  // Locates the query entry in the state using the transaction ID provided in `action.meta`.
45052
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.
45053
45465
  if (entry) {
45054
- // If the entry is found, the error information from `action.payload.error` is added to the `errors` array of the entry.
45055
- // This allows tracking of all errors that have occurred during the execution of the query.
45056
45466
  entry.errors.push(action.payload.error);
45057
45467
  // The status of the entry is updated to 'failed' to indicate that the query execution has encountered an error.
45058
45468
  entry.status = 'failed';
@@ -45087,6 +45497,9 @@ class QueryClientJob extends Observable {
45087
45497
  get status() {
45088
45498
  return this.#status$.value;
45089
45499
  }
45500
+ /**
45501
+ * An Observable stream of the job's status, emitting whenever the status changes.
45502
+ */
45090
45503
  get status$() {
45091
45504
  return this.#status$.asObservable();
45092
45505
  }
@@ -45206,8 +45619,10 @@ class QueryClientJob extends Observable {
45206
45619
  * This method should be called to gracefully end a job that has finished its task.
45207
45620
  * It will also attempt to cancel the job with a completion reason, transitioning its status to 'canceled'
45208
45621
  * if it was in an 'active' state but hadn't yet completed.
45622
+ * @param reason - Optional reason recorded for the completion/cancellation.
45209
45623
  */
45210
45624
  complete(reason) {
45625
+ // Only cancel/complete when the job isn't already in a terminal state
45211
45626
  if (!this.closed) {
45212
45627
  this.cancel(reason ?? `job: ${this.transaction} was completed`);
45213
45628
  }
@@ -45222,6 +45637,9 @@ class QueryClientJob extends Observable {
45222
45637
  const transaction = this.transaction;
45223
45638
  this.#client.cancel(transaction, reason ?? `job: ${transaction} was canceled`);
45224
45639
  }
45640
+ /**
45641
+ * Disposes of the job by completing it, releasing any held resources.
45642
+ */
45225
45643
  [Symbol.dispose]() {
45226
45644
  this.complete();
45227
45645
  }
@@ -45335,6 +45753,7 @@ class QueryClient extends Observable {
45335
45753
  * @returns {Observable<TType>} An Observable stream of successful results.
45336
45754
  */
45337
45755
  get success$() {
45756
+ // narrow the action stream down to successful execute actions and extract their payload
45338
45757
  return this.action$.pipe(filter(actions$1.execute.success.match), map$1(({ payload }) => payload));
45339
45758
  }
45340
45759
  /**
@@ -45343,6 +45762,7 @@ class QueryClient extends Observable {
45343
45762
  * @returns {Observable<QueryClientError>} An Observable stream of query errors.
45344
45763
  */
45345
45764
  get error$() {
45765
+ // narrow the action stream down to client errors and wrap them as QueryClientError instances
45346
45766
  return this.action$.pipe(filterAction('client/error'), withLatestFrom(this.#state), map$1(([action, state]) => {
45347
45767
  const { payload, meta: { transaction }, } = action;
45348
45768
  const request = state[transaction];
@@ -45373,7 +45793,9 @@ class QueryClient extends Observable {
45373
45793
  // Add flows to handle different aspects of the query lifecycle.
45374
45794
  this.#subscription.add(this.#state.addFlow(handleRequests));
45375
45795
  this.#subscription.add(this.#state.addFlow(handleExecution(queryFn)));
45376
- 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)));
45377
45799
  /**
45378
45800
  * Register effects to emit lifecycle events for different query actions.
45379
45801
  *
@@ -45426,6 +45848,7 @@ class QueryClient extends Observable {
45426
45848
  */
45427
45849
  query(args, opt) {
45428
45850
  const job = new QueryClientJob(this, args, opt);
45851
+ // share a single underlying job execution across all subscribers, replaying past emissions to late subscribers
45429
45852
  const job$ = job.pipe(share({ connector: () => new ReplaySubject(), resetOnRefCountZero: false }));
45430
45853
  Object.defineProperties(job$, {
45431
45854
  transaction: { get: () => job.transaction },
@@ -45435,6 +45858,8 @@ class QueryClient extends Observable {
45435
45858
  cancel: { value: (reason) => job.cancel(reason) },
45436
45859
  complete: { value: () => job.complete() },
45437
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.
45438
45863
  return job$;
45439
45864
  }
45440
45865
  /**
@@ -45463,7 +45888,10 @@ class QueryClient extends Observable {
45463
45888
  * @returns The request matching the reference, or `undefined` if not found.
45464
45889
  */
45465
45890
  getRequestByRef(ref) {
45466
- 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;
45467
45895
  }
45468
45896
  /**
45469
45897
  * Cancels a task by its reference string. If a reason is provided, it will be included
@@ -45473,6 +45901,7 @@ class QueryClient extends Observable {
45473
45901
  */
45474
45902
  cancelTaskByRef(ref, reason) {
45475
45903
  const entry = this.getRequestByRef(ref);
45904
+ // Only cancel when a matching request was actually found
45476
45905
  if (entry?.ref) {
45477
45906
  this.cancel(entry.transaction, reason);
45478
45907
  }
@@ -45484,6 +45913,7 @@ class QueryClient extends Observable {
45484
45913
  * @param {string} [reason] The reason for cancellation.
45485
45914
  */
45486
45915
  cancel(transaction, reason) {
45916
+ // No transaction targeted — cancel every tracked transaction
45487
45917
  if (!transaction) {
45488
45918
  // If no specific transaction is provided, iterate through all transactions
45489
45919
  // in the state and cancel each one, applying a generic cancellation reason.
@@ -45528,7 +45958,7 @@ class QueryClient extends Observable {
45528
45958
  * @template TArgs The type of the arguments associated with the cache entry.
45529
45959
  * @returns An object containing the cache actions.
45530
45960
  */
45531
- // 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
45532
45962
  function createActions() {
45533
45963
  return {
45534
45964
  /**
@@ -45620,6 +46050,7 @@ function createReducer (actions, initial = {}) {
45620
46050
  .addCase(actions.insert, (state, action) => {
45621
46051
  const { key, entry } = action.payload;
45622
46052
  const record = state[key];
46053
+ // Update the existing entry in place, or create a new one if none exists yet
45623
46054
  if (record) {
45624
46055
  // If the record exists, update the timestamp and increment the update count.
45625
46056
  record.updated = Date.now();
@@ -45647,8 +46078,10 @@ function createReducer (actions, initial = {}) {
45647
46078
  // Handles the 'invalidate' action to invalidate a cache record.
45648
46079
  .addCase(actions.invalidate, (state, action) => {
45649
46080
  const invalidKey = action.payload ? [action.payload] : Object.keys(state);
46081
+ // Invalidate either the single targeted key, or every key when none was specified
45650
46082
  for (const key of invalidKey) {
45651
46083
  const entry = state[key];
46084
+ // Only reset the timestamp when the record actually exists
45652
46085
  if (entry) {
45653
46086
  // reset the updated timestamp
45654
46087
  entry.updated = undefined;
@@ -45659,6 +46092,7 @@ function createReducer (actions, initial = {}) {
45659
46092
  .addCase(actions.mutate, (state, action) => {
45660
46093
  const { key, value, updated } = action.payload;
45661
46094
  const record = state[key];
46095
+ // Only apply the mutation when a record exists for this key
45662
46096
  if (record) {
45663
46097
  // Update the record with the new value and metadata.
45664
46098
  record.value = value;
@@ -45684,8 +46118,10 @@ function createReducer (actions, initial = {}) {
45684
46118
  .map(([key]) => key);
45685
46119
  // Remove keys that are not in the list of valid keys.
45686
46120
  if (currentKeys.length !== validKeys.length) {
46121
+ // Walk all current keys and drop the ones that were trimmed away
45687
46122
  for (const key of currentKeys) {
45688
46123
  const validKeyIndex = validKeys.indexOf(key);
46124
+ // The key survived trimming — remove it from the search list so later lookups are faster
45689
46125
  if (validKeyIndex !== -1) {
45690
46126
  // If the key is valid, remove it from the search list.
45691
46127
  validKeys.splice(validKeyIndex, 1);
@@ -45797,6 +46233,7 @@ class QueryCache {
45797
46233
  constructor(args) {
45798
46234
  const { trimming, initial } = args ?? {};
45799
46235
  this.#state = new FlowSubject(createReducer(actions, initial));
46236
+ // Only wire up automatic trimming when a trimming policy was provided
45800
46237
  if (trimming) {
45801
46238
  this.#state.addEffect('cache/set', () => this.#state.next(actions.trim(trimming)));
45802
46239
  }
@@ -45844,6 +46281,7 @@ class QueryCache {
45844
46281
  invalidate(key) {
45845
46282
  const item = key ? this.#state.value[key] : undefined;
45846
46283
  this.#state.next(actions.invalidate(key, item));
46284
+ // Only emit an event when a specific key was invalidated (not a full-cache clear)
45847
46285
  if (key) {
45848
46286
  this._registerEvent('query_cache_entry_invalidated', {
45849
46287
  key,
@@ -45855,9 +46293,12 @@ class QueryCache {
45855
46293
  * Mutates an item in the query cache by key.
45856
46294
  * @param {string} key - The key of the item to mutate.
45857
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`.
45858
46298
  */
45859
46299
  mutate(key, changes) {
45860
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
45861
46302
  if (!current) {
45862
46303
  throw new Error(`Cannot mutate cache item with key ${key}: item not found`);
45863
46304
  }
@@ -45881,6 +46322,7 @@ class QueryCache {
45881
46322
  const beforeKeys = new Set(Object.keys(this.#state.value));
45882
46323
  this.#state.next(actions.trim(options));
45883
46324
  const afterKeys = new Set(Object.keys(this.#state.value));
46325
+ // keys that existed before trimming but not after were the ones removed
45884
46326
  const removedKeys = Array.from(beforeKeys).filter((key) => !afterKeys.has(key));
45885
46327
  this._registerEvent('query_cache_trimmed', {
45886
46328
  data: {
@@ -45950,6 +46392,7 @@ class QueryTask extends Subject {
45950
46392
  #created = Date.now();
45951
46393
  /**
45952
46394
  * Gets the creation timestamp of the query task.
46395
+ * @returns The Unix timestamp (in milliseconds) when the task was created.
45953
46396
  */
45954
46397
  get created() {
45955
46398
  return this.#created;
@@ -45957,6 +46400,7 @@ class QueryTask extends Subject {
45957
46400
  #uuid = v4();
45958
46401
  /**
45959
46402
  * Gets the unique identifier of the query task.
46403
+ * @returns The task's generated UUID.
45960
46404
  */
45961
46405
  get uuid() {
45962
46406
  return this.#uuid;
@@ -45982,6 +46426,7 @@ class QueryTask extends Subject {
45982
46426
  * @returns A subscription to the job's result.
45983
46427
  */
45984
46428
  processJob(job) {
46429
+ // map the job's completion into a QueryTaskCompleted record for subscribers
45985
46430
  return job
45986
46431
  .pipe(map$1((result) => {
45987
46432
  const { key, uuid, created } = this;
@@ -46014,6 +46459,13 @@ class QueryEvent {
46014
46459
  type;
46015
46460
  key;
46016
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
+ */
46017
46469
  constructor(type, key, data) {
46018
46470
  this.type = type;
46019
46471
  this.key = key;
@@ -46051,10 +46503,12 @@ const defaultCacheValidator = (expires = 0) => (entry) => (entry.updated ?? 0) +
46051
46503
  * @returns A function that takes a query request and returns an Observable representing the queued request.
46052
46504
  */
46053
46505
  const getQueueOperator = (type = 'switch') => {
46506
+ // A custom queue function was provided, use it directly
46054
46507
  if (typeof type === 'function') {
46055
46508
  return type;
46056
46509
  }
46057
46510
  return (() => {
46511
+ // Map the named operator type to its concrete implementation
46058
46512
  switch (type) {
46059
46513
  case 'concat':
46060
46514
  return concatQueue;
@@ -46125,7 +46579,6 @@ const getQueueOperator = (type = 'switch') => {
46125
46579
  * @see {@link Query.invalidate} for more details on invalidating cache entries.
46126
46580
  * @see {@link QueueOperatorType} for more details on the available queue operators.
46127
46581
  */
46128
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
46129
46582
  class Query {
46130
46583
  /**
46131
46584
  * Static utility that extracts the raw value from a query result Observable.
@@ -46185,16 +46638,19 @@ class Query {
46185
46638
  #event$;
46186
46639
  /**
46187
46640
  * A public getter for the client instance.
46188
- * TODO: Implement a proxy to control access to the client.
46641
+ * TODO(#5144): Implement a proxy to control access to the client.
46189
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.
46190
46644
  */
46191
46645
  get client() {
46192
- // TODO: Proxy
46646
+ // TODO(#5144): Proxy
46193
46647
  return this.#client;
46194
46648
  }
46195
46649
  /**
46196
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`.
46197
46652
  * @param type - The event type
46653
+ * @param key - The cache/query key the event relates to
46198
46654
  * @param data - The event data
46199
46655
  */
46200
46656
  _registerEvent(type, key, data) {
@@ -46202,11 +46658,12 @@ class Query {
46202
46658
  }
46203
46659
  /**
46204
46660
  * A public getter for the cache instance.
46205
- * TODO: Implement a proxy to control access to the cache.
46661
+ * TODO(#5144): Implement a proxy to control access to the cache.
46206
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.
46207
46664
  */
46208
46665
  get cache() {
46209
- // TODO: Proxy
46666
+ // TODO(#5144): Proxy
46210
46667
  return this.#cache;
46211
46668
  }
46212
46669
  /**
@@ -46271,6 +46728,7 @@ class Query {
46271
46728
  // shutdown the queue when the query is completed
46272
46729
  this.#subscription.add(() => this.#queue$.complete());
46273
46730
  this.#subscription.add(this.#queue$
46731
+ // register queued events, then apply the queue operator to the ongoing tasks
46274
46732
  .pipe(tap((key) => {
46275
46733
  this._registerEvent('query_queued', key);
46276
46734
  }),
@@ -46400,9 +46858,11 @@ class Query {
46400
46858
  const { skipResolve, ...args } = opt || {};
46401
46859
  const fn = skipResolve ? firstValueFrom : lastValueFrom;
46402
46860
  return new Promise((resolve, reject) => {
46861
+ // Reject the promise early if the caller aborts via the signal
46403
46862
  if (opt?.signal) {
46404
46863
  opt.signal.addEventListener('abort', () => reject(new Error('Query aborted')));
46405
46864
  }
46865
+ // Throw if the query completes without emitting a value
46406
46866
  fn(this._query(payload, args).pipe(throwIfEmpty())).then(resolve, reject);
46407
46867
  });
46408
46868
  }
@@ -46424,7 +46884,7 @@ class Query {
46424
46884
  persistentQuery(args, options) {
46425
46885
  const key = this.#generateCacheKey(args);
46426
46886
  const original = this._query(args, options);
46427
- return new Observable((subscriber) => {
46887
+ const cacheAwareStream = new Observable((subscriber) => {
46428
46888
  subscriber.add(original.subscribe({
46429
46889
  next: subscriber.next.bind(subscriber),
46430
46890
  error: subscriber.error.bind(subscriber),
@@ -46433,6 +46893,7 @@ class Query {
46433
46893
  const validateCache = options?.cache?.validate || this.#validateCacheEntry;
46434
46894
  // Subscribe to the cache state and filter for the specific cache entry based on the key.
46435
46895
  subscriber.add(this.cache.state$
46896
+ // narrow the cache state stream down to just this key's entry
46436
46897
  .pipe(filter((x) => key in x), map$1((x) => ({
46437
46898
  ...x[key],
46438
46899
  key,
@@ -46440,9 +46901,9 @@ class Query {
46440
46901
  hasValidCache: validateCache(x[key], args),
46441
46902
  })))
46442
46903
  .subscribe(subscriber));
46443
- }).pipe(
46904
+ });
46444
46905
  // only emit when the transaction changes
46445
- distinctUntilChanged((a, b) => a.transaction === b.transaction &&
46906
+ return cacheAwareStream.pipe(distinctUntilChanged((a, b) => a.transaction === b.transaction &&
46446
46907
  a.mutated === b.mutated));
46447
46908
  }
46448
46909
  /**
@@ -46462,10 +46923,15 @@ class Query {
46462
46923
  *
46463
46924
  * @param args - The arguments that identify the specific cache entry to be mutated.
46464
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`.
46465
46929
  */
46466
46930
  mutate(args, changes, options) {
46467
46931
  const key = this.#generateCacheKey(args);
46932
+ // No existing cache entry for this key — decide whether to create one or fail
46468
46933
  if (key in this.cache.state === false) {
46934
+ // No explicit allowCreation setting provided — fail closed rather than silently creating
46469
46935
  if (options?.allowCreation === undefined) {
46470
46936
  throw new Error(`Cannot mutate cache item with key ${key}: item not found and option "allowCreation" is false`);
46471
46937
  }
@@ -46513,6 +46979,7 @@ class Query {
46513
46979
  * @returns A function that, when called, will unsubscribe the callback from further invalidation events.
46514
46980
  */
46515
46981
  onInvalidate(cb) {
46982
+ // only forward cache-invalidation actions to the callback
46516
46983
  const subscription = this.#cache.action$
46517
46984
  .pipe(filterAction('cache/invalidate'))
46518
46985
  .subscribe((action) => cb({ detail: { item: action.meta.item } }));
@@ -46527,6 +46994,7 @@ class Query {
46527
46994
  * @returns A function that, when called, will unsubscribe the callback from further mutation events.
46528
46995
  */
46529
46996
  onMutate(cb) {
46997
+ // only forward cache-mutation actions to the callback
46530
46998
  const subscription = this.#cache.action$
46531
46999
  .pipe(filterAction('cache/mutate'))
46532
47000
  .subscribe((action) => cb({ detail: { changes: action.payload, current: action.meta.item } }));
@@ -46584,7 +47052,9 @@ class Query {
46584
47052
  _createTask(key, args, options) {
46585
47053
  // Create a new Observable that represents the query task and will be returned to the caller.
46586
47054
  return new Observable((subscriber) => {
47055
+ // Support cancellation via an AbortSignal
46587
47056
  if (options?.signal) {
47057
+ // Already aborted before subscription — complete immediately without doing any work
46588
47058
  if (options?.signal.aborted) {
46589
47059
  this._registerEvent('query_aborted', key);
46590
47060
  return subscriber.complete();
@@ -46616,6 +47086,7 @@ class Query {
46616
47086
  // the status indicating that this is a cached response, and a flag indicating whether the cache
46617
47087
  // is considered valid based on the validation logic.
46618
47088
  subscriber.next(record);
47089
+ // Complete without fetching when the cache is valid, or invalid entries are intentionally suppressed
46619
47090
  if (hasValidCache || suppressInvalid) {
46620
47091
  this._registerEvent('query_completed', key, { data: cacheEntry.value, hasValidCache });
46621
47092
  // If the cache is valid, or if invalid cache entries should be suppressed (not re-fetched),
@@ -46629,6 +47100,7 @@ class Query {
46629
47100
  }
46630
47101
  // If the cache entry does not exist or is invalid, proceed to queue a new query request.
46631
47102
  const isExistingTask = key in this.#tasks;
47103
+ // No task is already tracking this key — create one and register it before queuing
46632
47104
  if (!isExistingTask) {
46633
47105
  this.#tasks[key] = new QueryTask(key, args, options);
46634
47106
  this._registerEvent('query_job_created', key, {
@@ -46707,7 +47179,9 @@ const serviceResponseSelector = (response, postProcess) => {
46707
47179
  const result$ = from(jsonSelector(response)).pipe(
46708
47180
  // parse and validate the response
46709
47181
  map$1((value) => ApiServices.default([]).parse(value)));
47182
+ // Only apply a transform pipeline when the caller supplied one
46710
47183
  if (postProcess) {
47184
+ // Apply the caller-supplied operator (e.g. session overrides) before emitting
46711
47185
  return result$.pipe(postProcess);
46712
47186
  }
46713
47187
  return result$;
@@ -46758,7 +47232,9 @@ class ServiceDiscoveryClient {
46758
47232
  /** {@inheritDoc IServiceDiscoveryClient.resolveService} */
46759
47233
  async resolveService(key, allow_cache) {
46760
47234
  const services = await this.resolveServices(allow_cache);
47235
+ // Locate the service matching the requested key
46761
47236
  const service = services.find((s) => s.key === key);
47237
+ // Fail loudly when the requested key doesn't resolve to a known service
46762
47238
  if (!service) {
46763
47239
  throw Error(`Failed to resolve service, invalid key [${key}]`);
46764
47240
  }
@@ -46799,6 +47275,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46799
47275
  * @throws {Error} When the HTTP module is not registered.
46800
47276
  */
46801
47277
  async _createConfig(init, initial) {
47278
+ // The service discovery client depends on the http module being registered
46802
47279
  if (!init.hasModule('http')) {
46803
47280
  throw new Error('http module is required');
46804
47281
  }
@@ -46806,6 +47283,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46806
47283
  if (!this._has('discoveryClient')) {
46807
47284
  // check if http module has a client with key 'service_discovery'
46808
47285
  const httpProvider = await init.requireInstance('http');
47286
+ // Auto-configure only when a matching http client has actually been registered
46809
47287
  if (httpProvider.hasClient('service_discovery')) {
46810
47288
  this.configureServiceDiscoveryClientByClientKey('service_discovery');
46811
47289
  }
@@ -46822,6 +47300,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46822
47300
  * @throws {Error} When `discoveryClient` has not been configured.
46823
47301
  */
46824
47302
  _processConfig(config, init) {
47303
+ // A discovery client is mandatory before the config can be considered complete
46825
47304
  if (!config.discoveryClient) {
46826
47305
  throw new Error('discoveryClient is required, please configure it');
46827
47306
  }
@@ -46876,6 +47355,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46876
47355
  configureServiceDiscoveryClient(configCallback) {
46877
47356
  this.setServiceDiscoveryClient(async (args) => {
46878
47357
  const { httpClient, endpoint } = (await lastValueFrom(from(configCallback(args)))) ?? {};
47358
+ // Only construct the client when the factory actually returned an http client
46879
47359
  if (httpClient) {
46880
47360
  return new ServiceDiscoveryClient({
46881
47361
  http: httpClient,
@@ -46891,7 +47371,9 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46891
47371
  // Check if there are any session overrides in session storage.
46892
47372
  try {
46893
47373
  const sessionOverrides = JSON.parse(storage.getItem('overriddenServiceDiscoveryUrls') || '{}');
47374
+ // Apply each session override onto the service matching its key
46894
47375
  for (const [key, { url, scopes }] of Object.entries(sessionOverrides)) {
47376
+ // Locate the service this override applies to
46895
47377
  const service = input.find((service) => service.key === key);
46896
47378
  // If the service can be found, override the values with the values
46897
47379
  // from session override.
@@ -46903,6 +47385,8 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46903
47385
  }
46904
47386
  }
46905
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.
46906
47390
  console.error('Failed to JSON parse session overrides: "overriddenServiceDiscoveryUrls"', e);
46907
47391
  }
46908
47392
  return input;
@@ -46945,7 +47429,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
46945
47429
  }
46946
47430
 
46947
47431
  // Generated by genversion.
46948
- const version$1 = '10.0.1';
47432
+ const version$1 = '10.0.2';
46949
47433
 
46950
47434
  /**
46951
47435
  * Default implementation of {@link IServiceDiscoveryProvider}.
@@ -46980,6 +47464,7 @@ class ServiceDiscoveryProvider extends BaseModuleProvider {
46980
47464
  /** {@inheritDoc IServiceDiscoveryProvider.createClient} */
46981
47465
  async createClient(name, opt) {
46982
47466
  const service = await this.resolveService(name);
47467
+ // A missing service configuration means the http client can't be constructed
46983
47468
  if (!service) {
46984
47469
  throw Error(`Could not load configuration of service [${name}]`);
46985
47470
  }
@@ -47159,7 +47644,7 @@ class BaseTelemetryAdapter {
47159
47644
  * without performing any initialization. It calls the protected `_initialize` method which
47160
47645
  * subclasses can override to provide custom initialization logic.
47161
47646
  *
47162
- * TODO: Consider changing return type from Promise<void> to Promise<TelemetryItem[]>
47647
+ * TODO(#5112): Consider changing return type from Promise<void> to Promise<TelemetryItem[]>
47163
47648
  * to allow adapters to return telemetry items (errors/warnings) that occurred during
47164
47649
  * initialization, eliminating the chicken-and-egg problem with reporting init errors.
47165
47650
  *
@@ -47226,6 +47711,7 @@ class ConsoleAdapter extends BaseTelemetryAdapter {
47226
47711
  * @returns CSS background color string
47227
47712
  */
47228
47713
  _generateTitleBackground(lvl) {
47714
+ // Pick a background color matching the severity of this telemetry level
47229
47715
  switch (lvl) {
47230
47716
  case TelemetryLevel.Critical:
47231
47717
  return 'rgb(255, 0, 0)';
@@ -47252,12 +47738,12 @@ class ConsoleAdapter extends BaseTelemetryAdapter {
47252
47738
  * @returns Formatted time string (ms, s, or m s format)
47253
47739
  */
47254
47740
  _formatMetric = (value) => {
47741
+ // Show milliseconds for values under 1 second
47255
47742
  if (value < 1000) {
47256
- // Show milliseconds for values under 1 second
47257
47743
  return `${Math.round(value)}ms`;
47258
47744
  }
47745
+ // Show seconds with 1 decimal for values under 1 minute
47259
47746
  if (value < 60000) {
47260
- // Show seconds with 1 decimal for values under 1 minute
47261
47747
  return `${(value / 1000).toFixed(1)}s`;
47262
47748
  }
47263
47749
  // For longer durations, show minutes and seconds
@@ -47299,6 +47785,7 @@ class ConsoleAdapter extends BaseTelemetryAdapter {
47299
47785
  metadata,
47300
47786
  scope,
47301
47787
  };
47788
+ // Dispatch to the right console format based on the telemetry item's type
47302
47789
  switch (type) {
47303
47790
  case TelemetryType.Event:
47304
47791
  // Log events with base data
@@ -47325,6 +47812,7 @@ class ConsoleAdapter extends BaseTelemetryAdapter {
47325
47812
  * @param msg - Additional message arguments to log
47326
47813
  */
47327
47814
  _log(lvl, title, ...msg) {
47815
+ // Pick the console method matching this telemetry level's severity
47328
47816
  switch (lvl) {
47329
47817
  case TelemetryLevel.Critical:
47330
47818
  case TelemetryLevel.Error:
@@ -47358,10 +47846,15 @@ class ConsoleAdapter extends BaseTelemetryAdapter {
47358
47846
  */
47359
47847
  const createPortalEntryPoint = (...segments) => {
47360
47848
  const normalized = segments
47849
+ // Drop undefined/null segments before further processing
47361
47850
  .filter((segment) => segment !== undefined && segment !== null)
47851
+ // Trim stray whitespace from each segment
47362
47852
  .map((segment) => segment.trim())
47853
+ // Drop any segments that became empty after trimming
47363
47854
  .filter(Boolean)
47855
+ // Strip leading slashes from every segment (and trailing slashes from the first)
47364
47856
  .map((segment, index) => {
47857
+ // The first segment may have a trailing slash (e.g. a base URL) that needs stripping
47365
47858
  if (index === 0) {
47366
47859
  return segment.replace(/\/+$/g, '');
47367
47860
  }
@@ -47402,6 +47895,7 @@ const createPortalEntryPoint = (...segments) => {
47402
47895
  */
47403
47896
  async function registerServiceWorker(framework) {
47404
47897
  const telemetry = framework.telemetry;
47898
+ // Bail out early when the browser has no service worker support at all
47405
47899
  if ('serviceWorker' in navigator === false) {
47406
47900
  const exception = new Error('Service workers are not supported in this browser.');
47407
47901
  exception.name = 'ServiceWorkerNotSupported';
@@ -47412,6 +47906,7 @@ async function registerServiceWorker(framework) {
47412
47906
  throw exception;
47413
47907
  }
47414
47908
  const resourceConfigs = import.meta.env.FUSION_SPA_SERVICE_WORKER_RESOURCES;
47909
+ // The worker needs a resource config to know which requests to intercept
47415
47910
  if (!resourceConfigs) {
47416
47911
  const exception = new Error('Service worker config is not defined.');
47417
47912
  exception.name = 'ServiceWorkerConfigNotDefined';
@@ -47435,10 +47930,12 @@ async function registerServiceWorker(framework) {
47435
47930
  navigator.serviceWorker.startMessages();
47436
47931
  // listen for messages from the service worker (set up before registration)
47437
47932
  navigator.serviceWorker.addEventListener('message', async (event) => {
47933
+ // Only the GET_TOKEN message type requests a token from this handler
47438
47934
  if (event.data.type === 'GET_TOKEN') {
47439
47935
  try {
47440
47936
  // extract scopes from the event data
47441
47937
  const scopes = event.data.scopes;
47938
+ // Scopes must be a real array before they can be used to request a token
47442
47939
  if (!scopes || !Array.isArray(scopes)) {
47443
47940
  const error = new Error('Invalid scopes provided');
47444
47941
  error.name = 'InvalidScopesProvided';
@@ -47446,6 +47943,7 @@ async function registerServiceWorker(framework) {
47446
47943
  }
47447
47944
  // request a token from the MSAL module
47448
47945
  const token = await framework.auth.acquireToken({ request: { scopes } });
47946
+ // A missing token means acquisition failed and the worker can't proceed
47449
47947
  if (!token) {
47450
47948
  const error = new Error('Failed to acquire token');
47451
47949
  error.name = 'FailedToAcquireToken';
@@ -47491,9 +47989,11 @@ async function registerServiceWorker(framework) {
47491
47989
  if (registration.waiting) {
47492
47990
  sendConfigToServiceWorker(registration.waiting);
47493
47991
  }
47992
+ // A worker still installing needs to reach the 'activated' state before it can receive config
47494
47993
  if (registration.installing) {
47495
47994
  registration.installing.addEventListener('statechange', (event) => {
47496
47995
  const worker = event.target;
47996
+ // Only send config once the worker has fully activated
47497
47997
  if (worker.state === 'activated') {
47498
47998
  sendConfigToServiceWorker(worker);
47499
47999
  }
@@ -47501,6 +48001,7 @@ async function registerServiceWorker(framework) {
47501
48001
  }
47502
48002
  // Listen for controller changes (happens during hard refresh or updates)
47503
48003
  navigator.serviceWorker.addEventListener('controllerchange', () => {
48004
+ // Only send config once this page is actually controlled by a worker
47504
48005
  if (navigator.serviceWorker.controller) {
47505
48006
  sendConfigToServiceWorker(navigator.serviceWorker.controller);
47506
48007
  }
@@ -47514,6 +48015,7 @@ async function registerServiceWorker(framework) {
47514
48015
  });
47515
48016
  // ensure we have an active service worker before sending config
47516
48017
  const activeWorker = readyRegistration.active;
48018
+ // Without an active worker there's nothing to send config to
47517
48019
  if (!activeWorker) {
47518
48020
  console.error('[Service Worker Registration] Service worker is not active after ready state');
47519
48021
  return;
@@ -47533,6 +48035,7 @@ async function registerServiceWorker(framework) {
47533
48035
  navigator.serviceWorker.addEventListener('controllerchange', onControllerChange);
47534
48036
  // Polling fallback and timeout to prevent infinite waiting
47535
48037
  checkInterval = setInterval(() => {
48038
+ // Stop polling once a controller has taken over
47536
48039
  if (navigator.serviceWorker.controller)
47537
48040
  finish();
47538
48041
  }, 200);
@@ -47560,7 +48063,7 @@ async function registerServiceWorker(framework) {
47560
48063
  }
47561
48064
 
47562
48065
  // Generated by genversion.
47563
- const version = '4.0.13';
48066
+ const version = '4.0.14';
47564
48067
 
47565
48068
  // Allow dynamic import without vite
47566
48069
  const importWithoutVite = (path) => import(/* @vite-ignore */ path);
@@ -47606,6 +48109,7 @@ enableTelemetry(configurator, {
47606
48109
  },
47607
48110
  // biome-ignore lint/suspicious/noExplicitAny: we need to use any here to allow dynamic properties
47608
48111
  };
48112
+ // Only attach user metadata when the auth module has resolved an account
47609
48113
  if (modules?.auth) {
47610
48114
  metadata.fusion.user = {
47611
48115
  id: modules.auth.account?.homeAccountId,
@@ -47670,8 +48174,8 @@ enableTelemetry(configurator, {
47670
48174
  document.body.innerHTML = '';
47671
48175
  document.body.appendChild(el);
47672
48176
  const portalEntryPoint = createPortalEntryPoint(portalProxy ? '/portal-proxy' : undefined, portal_manifest.build.assetPath, portal_manifest.build.templateEntry);
47673
- // @todo: should test if the entrypoint is external or internal
47674
- // @todo: add proper return type
48177
+ // TODO(#5066): should test if the entrypoint is external or internal
48178
+ // TODO(#5066): add proper return type
47675
48179
  const { render } = await measurement
47676
48180
  .clone()
47677
48181
  .resolve(importWithoutVite(portalEntryPoint), {