@equinor/fusion-framework-vite-plugin-spa 5.2.4-next.2 → 5.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,5 @@
1
- import { t as telemetryExportRequest, c as createErrorClass, _ as __extends, e as errorContext, E as EMPTY_SUBSCRIPTION, S as Subscription, O as Observable, a as __values, b as arrRemove, d as __spreadArray, f as __read, i as isFunction$1, g as SafeSubscriber, h as EmptyError, o as operate, j as executeSchedule, k as innerFrom, l as createOperatorSubscriber, m as map, n as mergeMap, p as identity, q as from, r as popScheduler, s as isArrayLike, u as isScheduler, v as popNumber, w as EMPTY, x as noop, y as mergeInternals, z as scanInternals, A as popResultSelector, B as reduce, C as lastValueFrom, M as ModuleEventLevel, D as filter, F as tap, G as BaseModuleProvider, H as catchError, I as of, J as object, K as _instanceof, L as _enum, N as string, P as boolean, Q as record, R as union, T as finalize$1, U as __rest, V as __assign, W as switchMap, X as observeOn, Y as deepmerge, Z as BaseConfigBuilder, $ as operationalIdentifier, a0 as defer, a1 as TelemetryExceptionSchema, a2 as TelemetryItemNames, a3 as TelemetryType, a4 as telemetryProviderActions, a5 as operationalScope, a6 as createOperationalTelemetry, a7 as TelemetryEventSchema, a8 as TelemetryMetricSchema, a9 as TelemetryCustomEventSchema, aa as createOperationalRecords, ab as TelemetryLevel, ac as createDiagnosticContext, ad as createAction, ae as createAsyncAction, af as array, ag as enableMSAL } from '../module-BnhyaZvt.js';
2
- import { B as BaseTelemetryAdapter } from '../TelemetryAdapter-C6KyX3cq.js';
3
- import '../trace-api-DzxUyN-s.js';
4
-
5
- /**
6
- * Marks request options as exporter-owned so operational instrumentation skips the export itself.
7
- * The marker applies only to this request, not unrelated concurrent traffic.
8
- * @param init - Options forwarded through the export transport's request pipeline.
9
- * @returns A copy retaining request options with the telemetry export request marker.
10
- * @template T - Request options type.
11
- * @example
12
- * ```ts
13
- * client.fetch('/v1/logs', markTelemetryExportRequest({ method: 'POST', body }));
14
- * ```
15
- */
16
- function markTelemetryExportRequest(init) {
17
- return { ...init, [telemetryExportRequest]: true };
18
- }
1
+ import { c as createErrorClass, _ as __extends, e as errorContext, E as EMPTY_SUBSCRIPTION, S as Subscription, O as Observable, a as __values, b as arrRemove, d as __spreadArray, f as __read, i as isFunction$1, g as SafeSubscriber, h as EmptyError, o as operate, j as executeSchedule, k as innerFrom, l as createOperatorSubscriber, m as map, n as mergeMap, p as identity, q as isArrayLike, r as isScheduler, s as popScheduler, t as popNumber, u as EMPTY, v as from, w as mergeInternals, x as scanInternals, y as noop, z as popResultSelector, A as reduce, B as lastValueFrom, M as ModuleEventLevel, C as filter, D as BaseModuleProvider, F as catchError, G as of, H as object, I as _instanceof, J as _enum, K as string, L as boolean, N as record, P as union, Q as __rest, R as __assign, T as switchMap, U as observeOn, V as BaseConfigBuilder, W as TelemetryExceptionSchema, X as TelemetryItemNames, Y as TelemetryType, Z as TelemetryEventSchema, $ as TelemetryMetricSchema, a0 as TelemetryCustomEventSchema, a1 as array, a2 as TelemetryLevel, a3 as enableMSAL } from '../module-9J9oqYEu.js';
2
+ import { B as BaseTelemetryAdapter } from '../TelemetryAdapter-qfXdaj7x.js';
19
3
 
20
4
  var ObjectUnsubscribedError = createErrorClass(function (_super) {
21
5
  return function ObjectUnsubscribedErrorImpl() {
@@ -534,18 +518,6 @@ function mergeAll(concurrent) {
534
518
  return mergeMap(identity, concurrent);
535
519
  }
536
520
 
537
- function concatAll() {
538
- return mergeAll(1);
539
- }
540
-
541
- function concat() {
542
- var args = [];
543
- for (var _i = 0; _i < arguments.length; _i++) {
544
- args[_i] = arguments[_i];
545
- }
546
- return concatAll()(from(args, popScheduler(args)));
547
- }
548
-
549
521
  var nodeEventEmitterMethods = ['addListener', 'removeListener'];
550
522
  var eventTargetMethods = ['addEventListener', 'removeEventListener'];
551
523
  var jqueryMethods = ['on', 'off'];
@@ -689,12 +661,6 @@ function take(count) {
689
661
  });
690
662
  }
691
663
 
692
- function ignoreElements() {
693
- return operate(function (source, subscriber) {
694
- source.subscribe(createOperatorSubscriber(subscriber, noop));
695
- });
696
- }
697
-
698
664
  function distinctUntilChanged(comparator, keySelector) {
699
665
  if (keySelector === void 0) { keySelector = identity; }
700
666
  comparator = comparator !== null && comparator !== void 0 ? comparator : defaultCompare;
@@ -729,23 +695,14 @@ function defaultErrorFactory() {
729
695
  return new EmptyError();
730
696
  }
731
697
 
732
- function exhaustMap(project, resultSelector) {
698
+ function finalize$1(callback) {
733
699
  return operate(function (source, subscriber) {
734
- var index = 0;
735
- var innerSub = null;
736
- var isComplete = false;
737
- source.subscribe(createOperatorSubscriber(subscriber, function (outerValue) {
738
- if (!innerSub) {
739
- innerSub = createOperatorSubscriber(subscriber, undefined, function () {
740
- innerSub = null;
741
- isComplete && subscriber.complete();
742
- });
743
- innerFrom(project(outerValue, index++)).subscribe(innerSub);
744
- }
745
- }, function () {
746
- isComplete = true;
747
- !innerSub && subscriber.complete();
748
- }));
700
+ try {
701
+ source.subscribe(subscriber);
702
+ }
703
+ finally {
704
+ subscriber.add(callback);
705
+ }
749
706
  });
750
707
  }
751
708
 
@@ -923,6 +880,42 @@ function takeWhile(predicate, inclusive) {
923
880
  });
924
881
  }
925
882
 
883
+ function tap(observerOrNext, error, complete) {
884
+ var tapObserver = isFunction$1(observerOrNext) || error || complete
885
+ ?
886
+ { next: observerOrNext, error: error, complete: complete }
887
+ : observerOrNext;
888
+ return tapObserver
889
+ ? operate(function (source, subscriber) {
890
+ var _a;
891
+ (_a = tapObserver.subscribe) === null || _a === void 0 ? void 0 : _a.call(tapObserver);
892
+ var isUnsub = true;
893
+ source.subscribe(createOperatorSubscriber(subscriber, function (value) {
894
+ var _a;
895
+ (_a = tapObserver.next) === null || _a === void 0 ? void 0 : _a.call(tapObserver, value);
896
+ subscriber.next(value);
897
+ }, function () {
898
+ var _a;
899
+ isUnsub = false;
900
+ (_a = tapObserver.complete) === null || _a === void 0 ? void 0 : _a.call(tapObserver);
901
+ subscriber.complete();
902
+ }, function (err) {
903
+ var _a;
904
+ isUnsub = false;
905
+ (_a = tapObserver.error) === null || _a === void 0 ? void 0 : _a.call(tapObserver, err);
906
+ subscriber.error(err);
907
+ }, function () {
908
+ var _a, _b;
909
+ if (isUnsub) {
910
+ (_a = tapObserver.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(tapObserver);
911
+ }
912
+ (_b = tapObserver.finalize) === null || _b === void 0 ? void 0 : _b.call(tapObserver);
913
+ }));
914
+ })
915
+ :
916
+ identity;
917
+ }
918
+
926
919
  function withLatestFrom() {
927
920
  var inputs = [];
928
921
  for (var _i = 0; _i < arguments.length; _i++) {
@@ -984,19 +977,13 @@ const ModuleConfiguratorEventBaseName = 'ModuleConfigurator';
984
977
  */
985
978
  const ModuleConfiguratorEventName = {
986
979
  ModuleConfigAdded: `${ModuleConfiguratorEventBaseName}.module.configAdded`,
987
- PreparedConfigReplaced: `${ModuleConfiguratorEventBaseName}.module.preparedConfigReplaced`,
988
980
  OnConfiguredAdded: `${ModuleConfiguratorEventBaseName}.onConfigured.added`,
989
981
  OnInitializedAdded: `${ModuleConfiguratorEventBaseName}.onInitialized.added`,
990
982
  PluginAdded: `${ModuleConfiguratorEventBaseName}.plugin.added`,
991
983
  InitializeConfigLoaded: `${ModuleConfiguratorEventBaseName}.config.loaded`,
992
984
  InitializeInstanceInitialized: `${ModuleConfiguratorEventBaseName}.instance.initialized`,
993
985
  Initialize: `${ModuleConfiguratorEventBaseName}.initialize.complete`,
994
- HostInitializeStarted: `${ModuleConfiguratorEventBaseName}.host.initializeStarted`,
995
- HostInitialized: `${ModuleConfiguratorEventBaseName}.host.initialized`,
996
- HostInitializeError: `${ModuleConfiguratorEventBaseName}.host.initializeError`,
997
986
  ConfiguratorCreated: `${ModuleConfiguratorEventBaseName}.configurator.created`,
998
- ConfiguratorPrepared: `${ModuleConfiguratorEventBaseName}.configurator.prepared`,
999
- ConfiguratorPrepareError: `${ModuleConfiguratorEventBaseName}.configurator.prepareError`,
1000
987
  ConfiguratorFailed: `${ModuleConfiguratorEventBaseName}.configurator.failed`,
1001
988
  ModulePostConfigured: `${ModuleConfiguratorEventBaseName}.module.postConfigured`,
1002
989
  ModulePostConfigureError: `${ModuleConfiguratorEventBaseName}.module.postConfigureError`,
@@ -1061,10 +1048,7 @@ async function createModuleConfigs(ctx, ref) {
1061
1048
  const config$ = from(modules).pipe(mergeMap(async (module) => {
1062
1049
  const configStart = performance.now();
1063
1050
  try {
1064
- // Explicitly registered builders must not be replaced by another factory-created instance.
1065
- const configurator = ctx.configurators?.has(module)
1066
- ? ctx.configurators.get(module)
1067
- : await module.configure?.(ref);
1051
+ const configurator = await module.configure?.(ref);
1068
1052
  const configLoadTime = Math.round(performance.now() - configStart);
1069
1053
  registerEvent({
1070
1054
  level: ModuleEventLevel.Debug,
@@ -1204,8 +1188,8 @@ config) {
1204
1188
  * Runs the full configure lifecycle phase for a set of modules.
1205
1189
  *
1206
1190
  * Orchestrates three sub-steps in order:
1207
- * 1. {@link createModuleConfigs} — reuse prepared builders or call `configure(ref)` factories.
1208
- * 2. Apply all user-registered config callbacks (`_configs`) concurrently.
1191
+ * 1. {@link createModuleConfigs} — call each module's `configure(ref)` factory.
1192
+ * 2. Apply all user-registered config callbacks (`_configs`) sequentially.
1209
1193
  * 3. {@link runPostConfigureHooks} — call `postConfigure` and `afterConfiguration` hooks.
1210
1194
  *
1211
1195
  * @param ctx - The configure phase context.
@@ -1225,81 +1209,6 @@ async function runConfigurePhase(ctx, ref) {
1225
1209
  return config;
1226
1210
  }
1227
1211
 
1228
- /**
1229
- * Rejects asynchronous preparation without leaving rejected promises unobserved.
1230
- * @param result - Factory or preparation callback result.
1231
- * @param moduleName - Module to identify in the preparation error.
1232
- * @throws {TypeError} When preparation returned a promise or thenable.
1233
- */
1234
- function assertSynchronous(result, moduleName) {
1235
- // Preparation must finish before control returns to host configuration.
1236
- if (result !== null &&
1237
- (typeof result === 'object' || typeof result === 'function') &&
1238
- 'then' in result &&
1239
- typeof result.then === 'function') {
1240
- // Invalid async work cannot be canceled, but its eventual rejection must remain visible.
1241
- void Promise.resolve(result).catch(() => console.warn(`Asynchronous preparation failed for module "${moduleName}"`));
1242
- throw new TypeError(`Preparation for module "${moduleName}" must be synchronous`);
1243
- }
1244
- }
1245
- /**
1246
- * Runs synchronous preparation at the registration boundary, without exposing stored builders.
1247
- * Supplied builders bypass factories; opt-in factories run once per registered descriptor.
1248
- * The ordinary configure phase later receives the same object.
1249
- *
1250
- * Preparation does not mutate orchestrator state: it returns the builder to commit, so a
1251
- * failure leaves the previous registration untouched.
1252
- *
1253
- * @param ctx - Registration and the host's already prepared builders.
1254
- * @returns The builder to commit, or `undefined` for an ordinary, unprepared registration.
1255
- * @template TModule - Registered module descriptor.
1256
- * @template TConfig - Configuration callback type associated with the registration.
1257
- * @throws Rethrows preparation errors after emitting a lifecycle failure event.
1258
- * @internal
1259
- */
1260
- function runPreparePhase(ctx) {
1261
- const { registration, configurators, registerEvent } = ctx;
1262
- const { module, configurator, prepare } = registration;
1263
- // Ordinary registrations keep factory creation in the existing configure phase.
1264
- if (configurator === undefined && !prepare) {
1265
- return undefined;
1266
- }
1267
- try {
1268
- // An explicit builder wins; otherwise reuse this descriptor's prepared builder.
1269
- let builder = configurator ?? configurators.get(module);
1270
- // Only opted-in, reference-independent factories may run before initialize(ref).
1271
- if (builder === undefined) {
1272
- // Never freeze an ordinary factory's missing parent reference by invoking it early.
1273
- if (!module.prepare) {
1274
- throw new TypeError(`Module "${module.name}" requires a prepare factory or an explicit configurator for preparation`);
1275
- }
1276
- builder = module.prepare();
1277
- assertSynchronous(builder, module.name);
1278
- }
1279
- // Registered callbacks receive their module's builder only within this lifecycle boundary.
1280
- if (prepare) {
1281
- assertSynchronous(prepare(builder), module.name);
1282
- }
1283
- registerEvent({
1284
- level: ModuleEventLevel.Debug,
1285
- name: ModuleConfiguratorEventName.ConfiguratorPrepared,
1286
- message: `Configuration prepared for ${module.name}`,
1287
- properties: { moduleName: module.name },
1288
- });
1289
- return { configurator: builder };
1290
- }
1291
- catch (error) {
1292
- registerEvent({
1293
- level: ModuleEventLevel.Error,
1294
- name: ModuleConfiguratorEventName.ConfiguratorPrepareError,
1295
- message: `Configuration preparation failed for ${module.name}`,
1296
- properties: { moduleName: module.name },
1297
- error,
1298
- });
1299
- throw error;
1300
- }
1301
- }
1302
-
1303
1212
  /**
1304
1213
  * Creates a `requireInstance` resolver for use during module initialization.
1305
1214
  *
@@ -1403,7 +1312,6 @@ function createRequireInstance(moduleNames, instance$, registerEvent) {
1403
1312
  async function initializeModule(module, ctx) {
1404
1313
  const { config, ref, requireInstance, hasModule, registerEvent } = ctx;
1405
1314
  const key = module.name;
1406
- const moduleInitStart = performance.now();
1407
1315
  // Modules must expose an initialize method to participate in the initialize phase
1408
1316
  if (!module.initialize) {
1409
1317
  const error = new Error(`Module ${module.name} does not have initialize method`);
@@ -1417,7 +1325,6 @@ async function initializeModule(module, ctx) {
1417
1325
  moduleVersion: module.version?.toString() || 'unknown',
1418
1326
  },
1419
1327
  error,
1420
- metric: performance.now() - moduleInitStart,
1421
1328
  });
1422
1329
  throw error;
1423
1330
  }
@@ -1430,26 +1337,13 @@ async function initializeModule(module, ctx) {
1430
1337
  moduleVersion: module.version?.toString() || 'unknown',
1431
1338
  },
1432
1339
  });
1433
- let instance;
1434
- try {
1435
- instance = await module.initialize({
1436
- ref,
1437
- config: config[key],
1438
- requireInstance,
1439
- hasModule,
1440
- });
1441
- }
1442
- catch (error) {
1443
- registerEvent({
1444
- level: ModuleEventLevel.Error,
1445
- name: ModuleConfiguratorEventName.ModuleInitializeError,
1446
- message: 'Module initialization failed',
1447
- properties: { moduleName: module.name },
1448
- metric: performance.now() - moduleInitStart,
1449
- error,
1450
- });
1451
- throw error;
1452
- }
1340
+ const moduleInitStart = performance.now();
1341
+ const instance = await module.initialize({
1342
+ ref,
1343
+ config: config[key],
1344
+ requireInstance,
1345
+ hasModule,
1346
+ });
1453
1347
  // Warn when providers deviate from the expected base class — these modules
1454
1348
  // may lack version tracking and standard provider interfaces.
1455
1349
  if (!(instance instanceof BaseModuleProvider)) {
@@ -1497,8 +1391,7 @@ async function initializeModule(module, ctx) {
1497
1391
  /**
1498
1392
  * Runs the initialize lifecycle phase for all registered modules.
1499
1393
  *
1500
- * Initializes all modules concurrently and rejects on the first failure; providers that
1501
- * finish afterwards are passed to `onLateProvider` for cleanup. Cross-module
1394
+ * Initializes all modules **concurrently** using `mergeMap`. Cross-module
1502
1395
  * dependency ordering is handled lazily through `requireInstance`, which
1503
1396
  * waits for a peer module's `initialize` call to complete before resolving.
1504
1397
  *
@@ -1515,11 +1408,11 @@ async function initializeModule(module, ctx) {
1515
1408
  * initialize within its timeout window.
1516
1409
  */
1517
1410
  async function runInitializePhase(ctx, config, ref) {
1518
- const { modules, registerEvent, instances = {}, onLateProvider } = ctx;
1411
+ const { modules, registerEvent } = ctx;
1519
1412
  // Fast-path: no modules registered — return a sealed empty instance immediately
1520
1413
  // to avoid a subscribe/lastValueFrom race on an already-completed observable.
1521
1414
  if (modules.length === 0) {
1522
- return Object.seal(instances);
1415
+ return Object.seal({});
1523
1416
  }
1524
1417
  // Extract module names for dependency lookups before any module has initialized
1525
1418
  const moduleNames = modules.map((m) => m.name);
@@ -1530,56 +1423,55 @@ async function runInitializePhase(ctx, config, ref) {
1530
1423
  const requireInstance = createRequireInstance(moduleNames, instance$, registerEvent);
1531
1424
  // Initialize all modules concurrently; modules that depend on peers will
1532
1425
  // suspend inside requireInstance until the dependency resolves.
1426
+ const init$ = from(modules).pipe(mergeMap((module) => initializeModule(module, { config, ref, requireInstance, hasModule, registerEvent })));
1533
1427
  const initStart = performance.now();
1534
- let failed = false;
1535
- // Start every initializer at once; peers wait on each other through requireInstance.
1536
- const initializers = modules.map(async (module) => {
1537
- const [name, provider] = await initializeModule(module, {
1538
- config,
1539
- ref,
1540
- requireInstance,
1541
- hasModule,
1542
- registerEvent,
1543
- });
1544
- // Rollback already owns `instances`; a late provider is handed off rather than leaked.
1545
- if (failed) {
1546
- onLateProvider?.(module, provider);
1547
- return;
1548
- }
1549
- instances[name] = provider;
1550
- instance$.next({ ...instances });
1551
- });
1552
- try {
1553
- // Reject on the first failure: a sibling that never settles must not hang startup.
1554
- await Promise.all(initializers);
1555
- }
1556
- catch (error) {
1557
- failed = true;
1558
- // Reject outstanding dependency waits instead of waiting for their timeout.
1559
- instance$.error(error);
1560
- throw error;
1561
- }
1562
- instance$.complete();
1563
- const initTime = Math.round(performance.now() - initStart);
1564
- registerEvent({
1565
- level: ModuleEventLevel.Debug,
1566
- name: ModuleConfiguratorEventName.ModuleInitializeComplete,
1567
- message: 'All modules initialized',
1568
- properties: { modules: Object.keys(instances).join(', '), loadTime: initTime },
1569
- metric: initTime,
1428
+ // Accumulate module providers into the shared instance subject as each resolves.
1429
+ // Completing the subject signals that all modules are initialized.
1430
+ init$.subscribe({
1431
+ next: ([name, module]) => {
1432
+ // Merge the newly initialized module into the shared instance map without dropping peers
1433
+ const nextInstance = Object.assign(instance$.value, { [name]: module });
1434
+ instance$.next(nextInstance);
1435
+ },
1436
+ error: (err) => {
1437
+ registerEvent({
1438
+ level: ModuleEventLevel.Error,
1439
+ name: ModuleConfiguratorEventName.ModuleInitializeError,
1440
+ message: `Failed to initialize module ${err.name || 'unknown'}`,
1441
+ error: err,
1442
+ });
1443
+ instance$.error(err);
1444
+ },
1445
+ complete: () => {
1446
+ const loadTime = Math.round(performance.now() - initStart);
1447
+ registerEvent({
1448
+ level: ModuleEventLevel.Debug,
1449
+ name: ModuleConfiguratorEventName.ModuleInitializeComplete,
1450
+ message: `All modules initialized in ${loadTime}ms`,
1451
+ properties: {
1452
+ modules: Object.keys(instance$.value).join(', '),
1453
+ loadTime,
1454
+ },
1455
+ metric: loadTime,
1456
+ });
1457
+ instance$.complete();
1458
+ },
1570
1459
  });
1460
+ const instanceInitStart = performance.now();
1461
+ const instance = await lastValueFrom(instance$);
1462
+ const initTime = Math.round(performance.now() - instanceInitStart);
1571
1463
  registerEvent({
1572
1464
  level: ModuleEventLevel.Debug,
1573
1465
  name: ModuleConfiguratorEventName.InitializeComplete,
1574
1466
  message: `Modules instance created in ${initTime}ms`,
1575
1467
  properties: {
1576
- modules: Object.keys(instances).join(', '),
1468
+ modules: Object.keys(instance).join(', '),
1577
1469
  initTime,
1578
1470
  },
1579
1471
  metric: initTime,
1580
1472
  });
1581
- Object.seal(instances);
1582
- return instances;
1473
+ Object.seal(instance);
1474
+ return instance;
1583
1475
  }
1584
1476
 
1585
1477
  // biome-ignore-all lint/suspicious/noExplicitAny: internal type-erased dispatch — the post-initialize phase forwards opaque instances without inspecting their concrete shapes
@@ -1818,8 +1710,6 @@ async function runPluginPhase(ctx, modules, ref) {
1818
1710
  });
1819
1711
  }
1820
1712
 
1821
- /** Fixed teardown order: ordinary modules first, so diagnostics and events can still record them. */
1822
- const disposePhaseOrder = ['default', 'diagnostics', 'events'];
1823
1713
  function getPluginTeardownName(teardown) {
1824
1714
  return typeof teardown === 'function' ? teardown.name || 'anonymous' : 'dispose';
1825
1715
  }
@@ -1834,9 +1724,9 @@ async function runPluginTeardown(teardown) {
1834
1724
  /**
1835
1725
  * Runs the dispose lifecycle phase for all registered modules.
1836
1726
  *
1837
- * Calls module `dispose` hooks concurrently within each fixed phase:
1838
- * default, diagnostics, then events. Rejections are isolated through
1839
- * `Promise.allSettled`. After all hooks settle, the event stream is completed.
1727
+ * Calls each module's `dispose` hook (if defined) concurrently via
1728
+ * `Promise.allSettled` so one failing module cannot block others from
1729
+ * being torn down. After all hooks settle, the event stream is completed.
1840
1730
  *
1841
1731
  * @param ctx - The dispose phase context.
1842
1732
  * @param instance - The initialized module instance map to tear down.
@@ -1846,8 +1736,6 @@ async function runPluginTeardown(teardown) {
1846
1736
  */
1847
1737
  async function runDisposePhase(ctx, instance, ref) {
1848
1738
  const { modules, registerEvent, event$, pluginTeardowns = [] } = ctx;
1849
- const start = performance.now();
1850
- let failed = false;
1851
1739
  registerEvent({
1852
1740
  level: ModuleEventLevel.Debug,
1853
1741
  name: ModuleConfiguratorEventName.Dispose,
@@ -1876,7 +1764,6 @@ async function runDisposePhase(ctx, instance, ref) {
1876
1764
  });
1877
1765
  }
1878
1766
  catch (err) {
1879
- failed = true;
1880
1767
  registerEvent({
1881
1768
  level: ModuleEventLevel.Warning,
1882
1769
  name: ModuleConfiguratorEventName.PluginDisposeError,
@@ -1893,98 +1780,65 @@ async function runDisposePhase(ctx, instance, ref) {
1893
1780
  message: 'Disposing modules',
1894
1781
  properties: { count: modules.length },
1895
1782
  });
1896
- const groups = {
1897
- default: [],
1898
- diagnostics: [],
1899
- events: [],
1900
- };
1901
- // Partition once so invalid runtime input cannot silently omit a provider's cleanup.
1902
- for (const module of modules) {
1903
- const phase = module.disposePhase ?? 'default';
1904
- // Only named lifecycle phases may delay cleanup until infrastructure shutdown.
1905
- if (disposePhaseOrder.includes(phase)) {
1906
- groups[phase].push(module);
1907
- }
1908
- else {
1909
- failed = true;
1783
+ // Dispose all modules concurrently; failures are isolated per module so
1784
+ // one bad teardown cannot leave other modules in an inconsistent state.
1785
+ await Promise.allSettled(modules
1786
+ // Only process modules that define a dispose hook
1787
+ .filter((module) => !!module.dispose)
1788
+ // Dispose each module that defines the hook
1789
+ .map(async (module) => {
1790
+ // Narrow the type for TypeScript; the filter above already guarantees this
1791
+ if (!module.dispose)
1792
+ return;
1793
+ try {
1794
+ await module.dispose({
1795
+ ref,
1796
+ modules: instance,
1797
+ instance: instance[module.name],
1798
+ });
1910
1799
  registerEvent({
1911
- level: ModuleEventLevel.Warning,
1912
- name: ModuleConfiguratorEventName.ModulesDispose,
1913
- message: `Unsupported disposal phase for module ${module.name}; disposing during the default phase`,
1914
- properties: { moduleName: module.name },
1915
- error: new TypeError('Unsupported module disposal phase'),
1800
+ level: ModuleEventLevel.Debug,
1801
+ name: ModuleConfiguratorEventName.ModuleDisposed,
1802
+ message: `Module ${module.name} disposed successfully`,
1803
+ properties: {
1804
+ moduleName: module.name,
1805
+ moduleVersion: module.version?.toString() || 'unknown',
1806
+ },
1916
1807
  });
1917
- groups.default.push(module);
1918
- }
1919
- }
1920
- // Diagnostic infrastructure stays alive through ordinary module teardown.
1921
- for (const phase of disposePhaseOrder) {
1922
- await Promise.allSettled(groups[phase]
1923
- // Only process modules that define a dispose hook
1924
- .filter((module) => !!module.dispose)
1925
- // Dispose each module that defines the hook
1926
- .map(async (module) => {
1927
- // Narrow the type for TypeScript; the filter above already guarantees this
1928
- if (!module.dispose)
1929
- return;
1930
- const moduleStart = performance.now();
1931
- try {
1932
- await module.dispose({
1933
- ref,
1934
- modules: instance,
1935
- instance: instance[module.name],
1936
- });
1937
- registerEvent({
1938
- level: ModuleEventLevel.Debug,
1939
- name: ModuleConfiguratorEventName.ModuleDisposed,
1940
- metric: performance.now() - moduleStart,
1941
- message: `Module ${module.name} disposed successfully`,
1942
- properties: {
1943
- moduleName: module.name,
1944
- moduleVersion: module.version?.toString() || 'unknown',
1945
- },
1946
- });
1947
- }
1948
- catch (err) {
1949
- failed = true;
1950
- registerEvent({
1951
- level: ModuleEventLevel.Warning,
1952
- name: ModuleConfiguratorEventName.ModuleDisposeError,
1953
- metric: performance.now() - moduleStart,
1954
- message: `Module ${module.name} dispose failed`,
1955
- properties: {
1956
- moduleName: module.name,
1957
- moduleVersion: module.version?.toString() || 'unknown',
1958
- },
1959
- error: err,
1960
- });
1961
- }
1962
- }));
1963
- // Emit exactly one host outcome before flushing sinks, including when no sinks exist.
1964
- if (phase === 'default') {
1808
+ }
1809
+ catch (err) {
1965
1810
  registerEvent({
1966
- level: failed ? ModuleEventLevel.Warning : ModuleEventLevel.Information,
1967
- name: ModuleConfiguratorEventName.ModulesDisposed,
1968
- message: 'Default module disposal complete; flushing lifecycle sinks',
1969
- metric: performance.now() - start,
1970
- properties: { count: modules.length, failed },
1811
+ level: ModuleEventLevel.Warning,
1812
+ name: ModuleConfiguratorEventName.ModuleDisposeError,
1813
+ message: `Module ${module.name} dispose failed`,
1814
+ properties: {
1815
+ moduleName: module.name,
1816
+ moduleVersion: module.version?.toString() || 'unknown',
1817
+ },
1818
+ error: err,
1971
1819
  });
1972
1820
  }
1973
- }
1821
+ }));
1822
+ registerEvent({
1823
+ level: ModuleEventLevel.Debug,
1824
+ name: ModuleConfiguratorEventName.ModulesDisposed,
1825
+ message: 'Module dispose complete',
1826
+ properties: { count: modules.length },
1827
+ });
1974
1828
  // Complete the event stream last so all dispose events are captured before
1975
1829
  // any subscriber teardown triggered by completion.
1976
1830
  event$.complete();
1977
1831
  }
1978
1832
 
1979
1833
  // Generated by genversion.
1980
- const version$4 = '6.2.0-next.0';
1834
+ const version$4 = '6.1.6';
1981
1835
 
1982
1836
  // 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
1983
1837
  /* eslint-disable @typescript-eslint/no-explicit-any */
1984
1838
  /**
1985
1839
  * Core orchestrator that drives the module lifecycle in Fusion Framework.
1986
1840
  *
1987
- * `ModulesConfigurator` manages the full **prepare → configure → post-configure → initialize →
1841
+ * `ModulesConfigurator` manages the full **configure → post-configure → initialize →
1988
1842
  * post-initialize → plugin → dispose** pipeline for a set of modules. Consumers
1989
1843
  * register modules via {@link addConfig} or {@link configure}, then call
1990
1844
  * {@link initialize} to produce a sealed {@link ModulesInstance} whose properties
@@ -1994,7 +1848,6 @@ const version$4 = '6.2.0-next.0';
1994
1848
  *
1995
1849
  * | # | Phase | Entry point | Description |
1996
1850
  * |---|-------|-------------|-------------|
1997
- * | 0 | **Prepare** | `addConfig` | Opt-in synchronous preparation runs during registration, before startup callbacks can fail. |
1998
1851
  * | 1 | **Configure** | `_configure` | Each module's `configure()` factory creates a config builder; registered callbacks mutate it. |
1999
1852
  * | 2 | **Post-configure** | `_postConfigure` (inside `_configure`) | `postConfigure()` hooks and `onConfigured` callbacks run. |
2000
1853
  * | 3 | **Initialize** | `_initialize` | Modules are initialized concurrently; cross-module dependencies are resolved through `requireInstance`. |
@@ -2046,10 +1899,6 @@ class ModulesConfigurator {
2046
1899
  // configuration before telemetry is wired up.
2047
1900
  // Memory bound: ~24 KB at ~240 bytes/event × 100 events.
2048
1901
  #event$ = new ReplaySubject(100);
2049
- /** Shared disposal per initialized instance, so repeated calls join instead of re-running hooks. */
2050
- #disposals = new WeakMap();
2051
- /** Builders supplied or created during preparation, keyed by their exact module descriptor. */
2052
- #configurators = new Map();
2053
1902
  /**
2054
1903
  * Stream of lifecycle events emitted while modules are configured, initialized,
2055
1904
  * and disposed.
@@ -2100,14 +1949,10 @@ class ModulesConfigurator {
2100
1949
  * Teardown callbacks returned by registered plugins.
2101
1950
  *
2102
1951
  * Consumed during dispose and cleared after execution so repeated dispose calls
2103
- * do not run plugin cleanup more than once. Initialization now records teardowns per
2104
- * instance; this list only collects those from `_registerPlugins` overrides that do not
2105
- * forward the attempt's list.
1952
+ * do not run plugin cleanup more than once.
2106
1953
  * @protected
2107
1954
  */
2108
1955
  _pluginTeardowns = [];
2109
- /** Plugin teardowns owned by each initialized instance, so disposing one never tears down another. */
2110
- #instanceTeardowns = new WeakMap();
2111
1956
  /**
2112
1957
  * Set of all registered module descriptors.
2113
1958
  * Uses a `Set` for automatic deduplication — the same module registered twice
@@ -2180,22 +2025,14 @@ class ModulesConfigurator {
2180
2025
  *
2181
2026
  * Adds the module to the known module set and registers the optional
2182
2027
  * `configure`, `afterConfig`, and `afterInit` callbacks into their
2183
- * respective lifecycle phase arrays. An optional `prepare` callback runs
2184
- * synchronously during registration against the same builder.
2028
+ * respective lifecycle phase arrays.
2185
2029
  *
2186
2030
  * @param config - The module configurator descriptor to register.
2187
2031
  * @template T - The module type being registered.
2188
2032
  * @template TConfig - The resolved configuration type for the module.
2189
- * @throws Rethrows preparation failures; async preparation and unprepared ordinary factories are rejected.
2190
2033
  */
2191
2034
  addConfig(config) {
2192
2035
  const { module, afterConfig, afterInit, configure } = config;
2193
- // Prepare before touching registration state, so a failed preparation changes nothing.
2194
- const prepared = runPreparePhase({
2195
- registration: config,
2196
- configurators: this.#configurators,
2197
- registerEvent: (event) => this._registerEvent(event),
2198
- });
2199
2036
  // Find an existing descriptor so re-registering a name can replace all of its lifecycle hooks.
2200
2037
  const existingModule = Array.from(this._modules).find((m) => m.name === module.name);
2201
2038
  // Only handle replacement/dedupe when a descriptor is already registered under this name.
@@ -2206,19 +2043,7 @@ class ModulesConfigurator {
2206
2043
  // all share one module singleton) must stay additive so multiple calls can register
2207
2044
  // multiple named clients without clobbering each other.
2208
2045
  if (existingModule !== module) {
2209
- // A host-prepared builder (for example telemetry identity and startup capture) is lost
2210
- // with the old descriptor; duplicate package copies make this easy to miss, so say so.
2211
- if (this.#configurators.has(existingModule)) {
2212
- console.warn(`Prepared configuration for module "${module.name}" was replaced`);
2213
- this._registerEvent({
2214
- level: ModuleEventLevel.Warning,
2215
- name: ModuleConfiguratorEventName.PreparedConfigReplaced,
2216
- message: `Prepared configuration for module ${module.name} was replaced by a different descriptor`,
2217
- properties: { moduleName: module.name },
2218
- });
2219
- }
2220
2046
  this._removeModuleCallbacks(module.name);
2221
- this.#configurators.delete(existingModule);
2222
2047
  const modules = Array.from(this._modules)
2223
2048
  // Preserve every descriptor while substituting the newly registered module.
2224
2049
  .map((m) => (m.name === module.name ? module : m));
@@ -2228,10 +2053,6 @@ class ModulesConfigurator {
2228
2053
  else {
2229
2054
  this._modules.add(module);
2230
2055
  }
2231
- // Commit the prepared builder only once the registration itself is in place.
2232
- if (prepared) {
2233
- this.#configurators.set(module, prepared.configurator);
2234
- }
2235
2056
  this._registerEvent({
2236
2057
  level: ModuleEventLevel.Debug,
2237
2058
  name: ModuleConfiguratorEventName.ModuleConfigAdded,
@@ -2354,64 +2175,8 @@ class ModulesConfigurator {
2354
2175
  * @returns A promise resolving to the sealed, initialized module instance.
2355
2176
  * @template T - Additional modules to merge into the instance type.
2356
2177
  * @template R - The reference type, narrowed to `TRef`.
2357
- * @throws Rethrows the original configuration or initialization error after partial cleanup.
2358
2178
  */
2359
2179
  async initialize(ref) {
2360
- const start = performance.now();
2361
- const instances = {};
2362
- // Plugin cleanup belongs to this attempt, so rollback never drains another instance's.
2363
- const teardowns = [];
2364
- this._registerEvent({
2365
- level: ModuleEventLevel.Debug,
2366
- name: ModuleConfiguratorEventName.HostInitializeStarted,
2367
- message: 'Starting host initialization',
2368
- });
2369
- try {
2370
- const instance = await this._initializeModules(instances, teardowns, ref);
2371
- this._registerEvent({
2372
- level: ModuleEventLevel.Information,
2373
- name: ModuleConfiguratorEventName.HostInitialized,
2374
- message: 'Host initialized',
2375
- metric: performance.now() - start,
2376
- });
2377
- return instance;
2378
- }
2379
- catch (error) {
2380
- this._registerEvent({
2381
- level: ModuleEventLevel.Error,
2382
- name: ModuleConfiguratorEventName.HostInitializeError,
2383
- message: 'Host initialization failed',
2384
- metric: performance.now() - start,
2385
- error,
2386
- });
2387
- // Rollback owns the same provider map as this attempt, even when startup cannot return it.
2388
- try {
2389
- // Never call disposal hooks for modules that did not produce a provider.
2390
- const initializedModules = this.modules.filter((module) => Object.hasOwn(instances, module.name));
2391
- await runDisposePhase({
2392
- modules: initializedModules,
2393
- registerEvent: this._registerEvent.bind(this),
2394
- event$: this.#event$,
2395
- pluginTeardowns: teardowns,
2396
- }, instances, ref);
2397
- }
2398
- catch {
2399
- // Cleanup health is secondary; callers must receive the original startup rejection.
2400
- console.warn('Module initialization rollback failed');
2401
- }
2402
- throw error;
2403
- }
2404
- }
2405
- /**
2406
- * Runs initialization inside the host diagnostic boundary.
2407
- * @param instances - Attempt-owned provider map shared by initialization and rollback.
2408
- * @param teardowns - Attempt-owned plugin teardowns shared by rollback and disposal.
2409
- * @param ref - Reference forwarded to module lifecycle hooks.
2410
- * @returns The sealed module map.
2411
- * @template T - Additional modules in the resulting instance.
2412
- * @template R - Reference type accepted by this host.
2413
- */
2414
- async _initializeModules(instances, teardowns, ref) {
2415
2180
  const configStart = performance.now();
2416
2181
  const config = await this._configure(ref);
2417
2182
  const configLoadTime = Math.round(performance.now() - configStart);
@@ -2430,7 +2195,7 @@ class ModulesConfigurator {
2430
2195
  metric: configLoadTime,
2431
2196
  });
2432
2197
  const instanceStart = performance.now();
2433
- const instance = await this._initialize(config, ref, instances);
2198
+ const instance = await this._initialize(config, ref);
2434
2199
  const instanceLoadTime = Math.round(performance.now() - instanceStart);
2435
2200
  // Build a comma-separated module name list for telemetry properties
2436
2201
  // Extract just the module names before joining into a display string
@@ -2465,13 +2230,10 @@ class ModulesConfigurator {
2465
2230
  await this._postInitialize(instance, ref);
2466
2231
  // `instance` is the freshly built module instance record, which is structurally compatible
2467
2232
  // with `ModulesInstance<TModules>` but not nominally assignable across the generic params.
2468
- // The shorthand disposes the returned map itself, so it shares disposal with
2469
- // `configurator.dispose(modules)` and teardown never runs twice or concurrently.
2470
2233
  const modules = Object.seal(Object.assign({}, instance, {
2471
- dispose: () => this.dispose(modules),
2234
+ dispose: () => this.dispose(instance),
2472
2235
  }));
2473
- this.#instanceTeardowns.set(modules, teardowns);
2474
- await this._registerPlugins(modules, ref, teardowns);
2236
+ await this._registerPlugins(modules, ref);
2475
2237
  return modules;
2476
2238
  }
2477
2239
  /**
@@ -2509,7 +2271,6 @@ class ModulesConfigurator {
2509
2271
  async _configure(ref) {
2510
2272
  return runConfigurePhase({
2511
2273
  modules: this.modules,
2512
- configurators: this.#configurators,
2513
2274
  configs: this._configs,
2514
2275
  afterConfiguration: this._afterConfiguration,
2515
2276
  afterInit: this._afterInit,
@@ -2526,39 +2287,17 @@ class ModulesConfigurator {
2526
2287
  *
2527
2288
  * @param config - The merged module config map from the configure phase.
2528
2289
  * @param ref - Optional reference forwarded to each module's `initialize` call.
2529
- * @param instances - Attempt-owned provider map; forward to `super` when overriding to preserve rollback ownership.
2530
2290
  * @returns A promise resolving to the sealed map of initialized module providers.
2531
2291
  * @template T - Additional modules to merge into the instance type.
2532
2292
  * @template R - The reference type. Defaults to `TRef`.
2533
2293
  * @protected
2534
2294
  */
2535
- async _initialize(config, ref, instances = {}) {
2295
+ async _initialize(config, ref) {
2536
2296
  return runInitializePhase({
2537
2297
  modules: this.modules,
2538
2298
  registerEvent: this._registerEvent.bind(this),
2539
- instances,
2540
- onLateProvider: (module, provider) => {
2541
- void ModulesConfigurator.#disposeLateProvider(module, provider, ref);
2542
- },
2543
2299
  }, config, ref);
2544
2300
  }
2545
- /**
2546
- * Disposes a provider that finished initializing after startup already rejected.
2547
- * The rollback has completed the event stream by then, so failures are only logged.
2548
- * @param module - Module whose initializer resolved after the rejection.
2549
- * @param provider - The late provider to dispose.
2550
- * @param ref - Reference forwarded to the module dispose hook.
2551
- * @returns A promise resolving once the dispose hook has settled.
2552
- */
2553
- static async #disposeLateProvider(module, provider, ref) {
2554
- try {
2555
- await module.dispose?.({ ref, modules: { [module.name]: provider }, instance: provider });
2556
- }
2557
- catch {
2558
- // Startup already rejected with the original error; a late cleanup failure must not surface.
2559
- console.warn('Module initialization rollback failed');
2560
- }
2561
- }
2562
2301
  /**
2563
2302
  * Runs the post-initialize lifecycle phase.
2564
2303
  *
@@ -2590,16 +2329,14 @@ class ModulesConfigurator {
2590
2329
  *
2591
2330
  * @param instance - The sealed module instance from the initialize phase.
2592
2331
  * @param ref - Optional reference forwarded to each plugin callback.
2593
- * @param teardowns - Attempt-owned teardown list; forward to `super` when overriding so
2594
- * disposing this instance runs only its own plugin cleanup.
2595
2332
  * @template T - Additional modules to merge into the instance type.
2596
2333
  * @template R - The reference type, narrowed to `TRef`.
2597
2334
  * @protected
2598
2335
  */
2599
- async _registerPlugins(instance, ref, teardowns = this._pluginTeardowns) {
2336
+ async _registerPlugins(instance, ref) {
2600
2337
  return runPluginPhase({
2601
2338
  plugins: this._plugins,
2602
- teardowns,
2339
+ teardowns: this._pluginTeardowns,
2603
2340
  registerEvent: this._registerEvent.bind(this),
2604
2341
  }, instance, ref);
2605
2342
  }
@@ -2613,26 +2350,14 @@ class ModulesConfigurator {
2613
2350
  * @param ref - Optional reference forwarded to module dispose hooks.
2614
2351
  * @returns A promise resolving when all modules have been disposed.
2615
2352
  */
2616
- dispose(instance, ref) {
2617
- // Idempotence belongs to each initialized instance; another instance must still be disposed.
2618
- let disposal = this.#disposals.get(instance);
2619
- // First disposal of this instance starts teardown; later calls join it.
2620
- if (!disposal) {
2621
- // Own teardowns first; the shared list only holds those from non-forwarding overrides.
2622
- const pluginTeardowns = [
2623
- ...(this.#instanceTeardowns.get(instance)?.splice(0) ?? []),
2624
- ...this._pluginTeardowns.splice(0),
2625
- ];
2626
- disposal = runDisposePhase({
2627
- modules: this.modules,
2628
- registerEvent: this._registerEvent.bind(this),
2629
- // ReplaySubject extends Subject — dispose only needs .complete() which both have.
2630
- event$: this.#event$,
2631
- pluginTeardowns,
2632
- }, instance, ref);
2633
- this.#disposals.set(instance, disposal);
2634
- }
2635
- return disposal;
2353
+ async dispose(instance, ref) {
2354
+ return runDisposePhase({
2355
+ modules: this.modules,
2356
+ registerEvent: this._registerEvent.bind(this),
2357
+ // ReplaySubject extends Subject — dispose only needs .complete() which both have.
2358
+ event$: this.#event$,
2359
+ pluginTeardowns: this._pluginTeardowns,
2360
+ }, instance, ref);
2636
2361
  }
2637
2362
  }
2638
2363
 
@@ -3269,7 +2994,7 @@ class HttpClientConfigurator {
3269
2994
  }
3270
2995
 
3271
2996
  // Generated by genversion.
3272
- const version$3 = '8.1.5-next.0';
2997
+ const version$3 = '8.1.4';
3273
2998
 
3274
2999
  /** URL protocols accepted as valid ad-hoc base URIs. */
3275
3000
  const SUPPORTED_PROTOCOLS = ['http:', 'https:', 'ws:', 'wss:'];
@@ -3995,10 +3720,9 @@ const module$3 = {
3995
3720
  // only attempt to acquire a token when the request actually declares scopes
3996
3721
  if (scopes.length) {
3997
3722
  /** TODO(#5143): should be try catch, check caller for handling */
3998
- const options = { request: { scopes } };
3999
- // A token for telemetry delivery belongs to the export, so it carries the same
4000
- // request-local marker; auth telemetry then skips it instead of recording the export.
4001
- const accessToken = await authProvider.acquireAccessToken(telemetryExportRequest in request ? markTelemetryExportRequest(options) : options);
3723
+ const accessToken = await authProvider.acquireAccessToken({
3724
+ request: { scopes },
3725
+ });
4002
3726
  // without a token there's nothing to attach, fall through to the default request
4003
3727
  if (accessToken) {
4004
3728
  const headers = new Headers(request.headers);
@@ -5134,6 +4858,84 @@ class FlowSubject extends Observable {
5134
4858
  }
5135
4859
  }
5136
4860
 
4861
+ /**
4862
+ * Internal helper utilities for constructing and inspecting Redux-style
4863
+ * lifecycle action types (`::request`, `::success`, `::failure`).
4864
+ */
4865
+ /**
4866
+ * Separates an action's base type from its lifecycle suffix.
4867
+ */
4868
+ const actionSuffixDivider = '::';
4869
+
4870
+ // 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
4871
+ // 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
4872
+ /**
4873
+ * taken from https://github.com/reduxjs/redux-toolkit/tree/master/packages/toolkit/src
4874
+ */
4875
+ /**
4876
+ * Creates the runtime action creator implementation shared by the overloads above.
4877
+ *
4878
+ * @param type - The action type to assign to created actions.
4879
+ * @param prepareAction - An optional function that prepares payload, metadata, and errors.
4880
+ * @returns A callable action creator with type and matching metadata.
4881
+ * @throws {Error} When the prepare callback does not return an action object.
4882
+ */
4883
+ function createAction(type, prepareAction) {
4884
+ function actionCreator(...args) {
4885
+ // Use the prepare callback when the caller needs custom payload construction.
4886
+ if (prepareAction) {
4887
+ const prepared = prepareAction(...args);
4888
+ // Reject invalid prepare callbacks before reading their action fields.
4889
+ if (!prepared) {
4890
+ throw new Error('prepareAction did not return an object');
4891
+ }
4892
+ // Merge optional metadata and error fields into the prepared action.
4893
+ return {
4894
+ type,
4895
+ payload: prepared.payload,
4896
+ ...('meta' in prepared && { meta: prepared.meta }),
4897
+ ...('error' in prepared && { error: prepared.error }),
4898
+ };
4899
+ }
4900
+ return { type, payload: args[0] };
4901
+ }
4902
+ actionCreator.toString = () => `${type}`;
4903
+ actionCreator.type = type;
4904
+ actionCreator.match = (action) => action.type === type;
4905
+ return actionCreator;
4906
+ }
4907
+
4908
+ // biome-ignore-all lint/suspicious/noExplicitAny: generic constraints mirror create-action.ts's ported redux-toolkit PrepareAction<any> dispatch pattern
4909
+ /**
4910
+ * Creates the runtime async action implementation shared by the overloads above.
4911
+ *
4912
+ * @param type - The base action type string.
4913
+ * @param request - A prepare function for the request action payload.
4914
+ * @param success - A prepare function for the success action payload.
4915
+ * @param failure - An optional prepare function for the failure action payload.
4916
+ * @returns An action creator with request, success, and optional failure actions.
4917
+ */
4918
+ function createAsyncAction(type, request, success, failure) {
4919
+ const action = createAction([type, 'request'].join(actionSuffixDivider), request);
4920
+ // Attach the success action only when the caller supplied its prepare function.
4921
+ if (success) {
4922
+ // Add the success action while preserving the request creator's identity.
4923
+ Object.assign(action, {
4924
+ success: createAction([type, 'success'].join(actionSuffixDivider), success),
4925
+ });
4926
+ }
4927
+ // Attach the failure action only for async workflows that support failure results.
4928
+ if (failure) {
4929
+ // Add the failure action alongside the existing request and success actions.
4930
+ Object.assign(action, {
4931
+ failure: createAction([type, 'failure'].join(actionSuffixDivider), failure),
4932
+ });
4933
+ }
4934
+ // `action` is progressively mutated with `success`/`failure` properties above, so its static
4935
+ // type can't reflect the final shape declared by this function's overload signatures.
4936
+ return action;
4937
+ }
4938
+
5137
4939
  function freezeDraftable(val) {
5138
4940
  return isDraftable(val) ? produce(val, () => { }) : val;
5139
4941
  }
@@ -5374,11 +5176,89 @@ function toObservable(input, ...args) {
5374
5176
  return of(input);
5375
5177
  }
5376
5178
 
5179
+ /**
5180
+ * Checks whether a metadata value is an object literal that should merge key by key.
5181
+ * Arrays and class instances such as `Error` are excluded so they are kept intact.
5182
+ *
5183
+ * @param value - Metadata value to inspect.
5184
+ * @returns `true` when the value is a plain object.
5185
+ */
5186
+ const isPlainMetadata = (value) => {
5187
+ // Primitives and null are kept as-is
5188
+ if (typeof value !== 'object' || value === null) {
5189
+ return false;
5190
+ }
5191
+ const prototype = Object.getPrototypeOf(value);
5192
+ return prototype === Object.prototype || prototype === null;
5193
+ };
5194
+ /**
5195
+ * Lists a metadata object's entries without `__proto__`, which would replace the prototype of an
5196
+ * object it is assigned to (GHSA-gcqx-w48m-w2h4).
5197
+ *
5198
+ * @param value - Metadata object whose entries are listed.
5199
+ * @returns The entries that are safe to assign.
5200
+ */
5201
+ const safeEntries = (value) =>
5202
+ // `JSON.parse` keeps `__proto__` as an own key, so untrusted metadata can carry one
5203
+ Object.entries(value).filter(([key]) => key !== '__proto__');
5204
+ /**
5205
+ * Copies plain objects and arrays from incoming metadata, dropping `__proto__` keys at any depth
5206
+ * so a value with no existing counterpart is cleaned the same way as a merged one.
5207
+ *
5208
+ * @param value - Incoming metadata value.
5209
+ * @returns A cleaned copy of plain objects and arrays; any other value as-is.
5210
+ */
5211
+ const sanitizeMetadata = (value) => {
5212
+ // Arrays may contain plain objects that carry `__proto__` keys
5213
+ if (Array.isArray(value)) {
5214
+ // Clean every item, since items never merge with an existing value
5215
+ return value.map(sanitizeMetadata);
5216
+ }
5217
+ // Class instances such as `Error` and primitives are kept as-is
5218
+ if (!isPlainMetadata(value)) {
5219
+ return value;
5220
+ }
5221
+ // Clean each nested value too, since none of them has an existing value to merge with
5222
+ const entries = safeEntries(value).map(([key, entryValue]) => [
5223
+ key,
5224
+ sanitizeMetadata(entryValue),
5225
+ ]);
5226
+ return Object.fromEntries(entries);
5227
+ };
5228
+ /**
5229
+ * Merges one metadata value into another: plain objects merge recursively, arrays are appended,
5230
+ * and any other value from `override` wins.
5231
+ *
5232
+ * @param base - Existing metadata value.
5233
+ * @param override - Metadata value that takes precedence.
5234
+ * @returns The merged value, as a new object or array when anything was merged.
5235
+ */
5236
+ const mergeMetadataValue = (base, override) => {
5237
+ // Lists of values accumulate rather than replace each other
5238
+ if (Array.isArray(base) && Array.isArray(override)) {
5239
+ // Clean incoming items before appending them to the existing list
5240
+ return base.concat(override.map(sanitizeMetadata));
5241
+ }
5242
+ // Anything other than two plain objects is replaced by the override
5243
+ if (!isPlainMetadata(base) || !isPlainMetadata(override)) {
5244
+ return sanitizeMetadata(override);
5245
+ }
5246
+ // Start from the base keys so keys absent from the override survive
5247
+ const result = Object.fromEntries(safeEntries(base));
5248
+ // Overlay each override key, recursing where both sides define it
5249
+ for (const [key, value] of safeEntries(override)) {
5250
+ result[key] = Object.hasOwn(base, key)
5251
+ ? mergeMetadataValue(base[key], value)
5252
+ : sanitizeMetadata(value);
5253
+ }
5254
+ return result;
5255
+ };
5377
5256
  /**
5378
5257
  * Merges two `MetaData` objects into a single object.
5379
5258
  *
5380
5259
  * If both `source` and `target` are undefined, returns `undefined`.
5381
5260
  * Otherwise, performs a deep merge of the two objects, with properties from `target` overriding those in `source`.
5261
+ * Neither input is mutated.
5382
5262
  *
5383
5263
  * @param source - The base `MetaData` object to merge from.
5384
5264
  * @param target - The `MetaData` object whose properties will override those in `source`.
@@ -5389,7 +5269,7 @@ const mergeMetadata = (source, target) => {
5389
5269
  if (!source && !target) {
5390
5270
  return undefined;
5391
5271
  }
5392
- return deepmerge(source ?? {}, target ?? {});
5272
+ return mergeMetadataValue(source ?? {}, target ?? {});
5393
5273
  };
5394
5274
  /**
5395
5275
  * Merges two partial `TelemetryItem` objects into a single `TelemetryItem`.
@@ -5430,14 +5310,12 @@ const mergeTelemetryItem = (target, source) => {
5430
5310
  * Configures telemetry settings for the application.
5431
5311
  *
5432
5312
  * The `TelemetryConfigurator` class extends `BaseConfigBuilder` to provide a fluent API for
5433
- * setting up telemetry adapters, metadata, default scopes, parent providers, host identity and
5434
- * lifecycle records. Every setter records configuration; nothing is resolved until the
5435
- * telemetry module builds its configuration.
5313
+ * setting up telemetry adapters, metadata, default scopes, and parent providers.
5436
5314
  *
5437
5315
  * @example
5438
5316
  * ```typescript
5439
5317
  * const configurator = new TelemetryConfigurator()
5440
- * .setAdapter('console', consoleAdapter)
5318
+ * .setAdapter(myAdapter)
5441
5319
  * .setMetadata({ app: 'my-app' })
5442
5320
  * .setDefaultScope(['user', 'session'])
5443
5321
  * .setParent(parentProvider);
@@ -5453,7 +5331,7 @@ const mergeTelemetryItem = (target, source) => {
5453
5331
  * @see ITelemetryConfigurator
5454
5332
  */
5455
5333
  class TelemetryConfigurator extends BaseConfigBuilder {
5456
- #adapters = {};
5334
+ #adaptersCallbacks = {};
5457
5335
  #metadata = [];
5458
5336
  /**
5459
5337
  * Creates a new `TelemetryConfigurator` and wires up the async adapter
@@ -5464,56 +5342,40 @@ class TelemetryConfigurator extends BaseConfigBuilder {
5464
5342
  // Configure async adapter resolution using mergeMap to handle Promise/Observable adapter factories
5465
5343
  this._set('adapters', (args) => {
5466
5344
  // Resolve every adapter factory concurrently and merge them into a single record
5467
- return from(Object.entries(this.#adapters)).pipe(mergeMap(([identifier, { configure }]) =>
5345
+ return from(Object.entries(this.#adaptersCallbacks)).pipe(mergeMap(([identifier, adapterFn]) =>
5468
5346
  // Drop adapters whose factory resolved to a falsy value
5469
- defer(() => from(configure(args))).pipe(filter((adapter) => !!adapter), map((adapter) => [identifier, adapter]), catchError(() => {
5470
- // Factory errors may carry credentials or URLs, so only the failure is reported.
5471
- console.warn('Telemetry adapter factory failed');
5472
- return EMPTY;
5473
- }))), scan((acc, [identifier, adapter]) => {
5347
+ from(adapterFn(args)).pipe(filter((adapter) => !!adapter), map((adapter) => [identifier, adapter]))), scan((acc, [identifier, adapter]) => {
5474
5348
  acc[identifier] = adapter;
5475
5349
  return acc;
5476
5350
  }, {}), defaultIfEmpty({}), shareReplay({ bufferSize: 1, refCount: true }));
5477
5351
  });
5478
- // Ownership is declared configuration: a failed explicit exporter still owns delivery,
5479
- // so its failure can never activate an ancestor's fallback exporter.
5480
- this._set('ownsExport', async () =>
5481
- // Any explicit declaration owns delivery.
5482
- Object.values(this.#adapters).some(({ explicit }) => explicit));
5483
5352
  // Configure metadata merging - handles multiple sync/async metadata sources
5484
5353
  this._set('metadata', async () => {
5485
5354
  const metadataItems = this.#metadata;
5486
5355
  return (...args) =>
5487
5356
  // Merge every registered metadata source in order, emitting the final accumulated result
5488
- from(metadataItems).pipe(concatMap((metadata) => toObservable(metadata, ...args)), scan((acc, current) => mergeMetadata(acc, current) ?? {}, {}), defaultIfEmpty({}), last(), shareReplay({ bufferSize: 1, refCount: true }));
5357
+ from(metadataItems).pipe(concatMap((metadata) => toObservable(metadata, ...args)), scan((acc, current) => mergeMetadata(acc, current) ?? {}, {}), last(), shareReplay({ bufferSize: 1, refCount: true }));
5489
5358
  });
5490
5359
  }
5491
5360
  /**
5492
5361
  * Registers a telemetry adapter with the configurator.
5493
5362
  *
5494
- * A non-fallback adapter is an explicit exporter: it owns delivery, so the parent's fallback
5495
- * exporters skip records this provider relays.
5496
- *
5497
5363
  * @param identifier - The key used to register and later resolve this adapter.
5498
- * @param adapter - The telemetry adapter to be added.
5364
+ * @param adapter - The telemetry adapter to be added. The adapter's identifier is used as the key.
5499
5365
  * @returns The current instance of the configurator for method chaining.
5500
5366
  */
5501
5367
  setAdapter(identifier, adapter) {
5502
- this.#adapters[identifier] = { configure: async () => adapter, explicit: !adapter.fallback };
5503
- return this;
5368
+ return this.configureAdapter(identifier, async () => adapter);
5504
5369
  }
5505
5370
  /**
5506
5371
  * Configures a telemetry adapter with the configurator.
5507
5372
  *
5508
- * A factory's adapter is unknown until it resolves, so it is declared as an explicit exporter;
5509
- * register fallback adapters with `setAdapter`.
5510
- *
5511
5373
  * @param identifier - The key used to register and later resolve this adapter.
5512
5374
  * @param adapterFn - A callback function that returns a telemetry adapter instance
5513
5375
  * @returns The current instance for method chaining
5514
5376
  */
5515
5377
  configureAdapter(identifier, adapterFn) {
5516
- this.#adapters[identifier] = { configure: adapterFn, explicit: true };
5378
+ this.#adaptersCallbacks[identifier] = adapterFn;
5517
5379
  return this;
5518
5380
  }
5519
5381
  /**
@@ -5549,15 +5411,6 @@ class TelemetryConfigurator extends BaseConfigBuilder {
5549
5411
  this._set('parent', parent);
5550
5412
  return this;
5551
5413
  }
5552
- /** @inheritdoc */
5553
- setSource(source) {
5554
- // Fail at configuration time; an invalid label would otherwise drop every built-in record.
5555
- if (source.source !== undefined && !operationalIdentifier.safeParse(source.source).success) {
5556
- throw new RangeError('Telemetry source kind must be a lowercase kebab-case identifier, such as "portal"');
5557
- }
5558
- this._set('source', Object.freeze({ ...source }));
5559
- return this;
5560
- }
5561
5414
  /**
5562
5415
  * Attaches an observable stream of telemetry items to the configurator.
5563
5416
  *
@@ -5568,20 +5421,6 @@ class TelemetryConfigurator extends BaseConfigBuilder {
5568
5421
  this._set('items$', item$);
5569
5422
  return this;
5570
5423
  }
5571
- /** @inheritdoc */
5572
- attachLifecycleItems(item$) {
5573
- this._set('lifecycleItems$', item$);
5574
- return this;
5575
- }
5576
- /** @inheritdoc */
5577
- hasLifecycleItems() {
5578
- return this._has('lifecycleItems$');
5579
- }
5580
- /** @inheritdoc */
5581
- setLifecycleItemsEnabled(enabled) {
5582
- this._set('lifecycleItemsEnabled', enabled);
5583
- return this;
5584
- }
5585
5424
  /**
5586
5425
  * Sets the filter configuration for telemetry items.
5587
5426
  *
@@ -5597,158 +5436,13 @@ class TelemetryConfigurator extends BaseConfigBuilder {
5597
5436
  }
5598
5437
 
5599
5438
  // Generated by genversion.
5600
- const version$2 = '8.0.0-next.3';
5439
+ const version$2 = '7.2.3';
5601
5440
 
5602
5441
  /**
5603
- * Represents a telemetry measurement that tracks elapsed time and reports measurement data
5604
- * to a telemetry provider. Supports cloning, resetting, and marking measurements as completed.
5442
+ * Represents a framework event with customizable details and behavior.
5605
5443
  *
5606
- * @remarks
5607
- * - The measurement starts timing upon instantiation.
5608
- * - Provides methods to measure elapsed time, reset the timer, clone the measurement, and
5609
- * automatically measure on disposal if not already measured.
5610
- * - Can be used to wrap asynchronous operations and automatically record their duration.
5611
- *
5612
- * @example
5613
- * ```typescript
5614
- * const measurement = new Measurement(provider, { name: 'operation' });
5615
- * // ... perform operation ...
5616
- * measurement.measure();
5617
- * ```
5618
- *
5619
- * @public
5620
- */
5621
- class Measurement {
5622
- #provider;
5623
- #data;
5624
- #startTime;
5625
- #measured;
5626
- /**
5627
- * Initializes a new instance of the Measurement class.
5628
- *
5629
- * @param provider - The telemetry provider responsible for handling telemetry data.
5630
- * @param data - The measurement data to be associated with this instance.
5631
- */
5632
- constructor(provider, data) {
5633
- this.#data = data;
5634
- this.#provider = provider;
5635
- this.#startTime = performance.now();
5636
- }
5637
- /**
5638
- * Creates a new `Measurement` instance that is a copy of the current one.
5639
- *
5640
- * @param resetOptions - Options to control which properties are preserved in the clone.
5641
- * - `preserveStartTime`: If `true`, the start time of the measurement is preserved in the clone.
5642
- * @returns A new `Measurement` instance with copied data and optionally preserved start time.
5643
- */
5644
- clone(resetOptions = {}) {
5645
- const clonedMeasurement = new Measurement(this.#provider, this.#data);
5646
- // Preserve the original start time when requested, otherwise the clone starts fresh
5647
- if (resetOptions.preserveStartTime) {
5648
- clonedMeasurement.#startTime = this.#startTime;
5649
- }
5650
- return clonedMeasurement;
5651
- }
5652
- /**
5653
- * Resets the measurement state.
5654
- *
5655
- * @param options - Optional settings for the reset operation.
5656
- * @param options.preserveStartTime - If true, the start time will not be reset; otherwise, it will be set to the current time.
5657
- *
5658
- * Resets the measured flag and, unless `preserveStartTime` is true, updates the start time to the current performance time.
5659
- */
5660
- reset(options) {
5661
- // Keep the existing start time when preserving, otherwise restart the clock
5662
- if (!options?.preserveStartTime) {
5663
- this.#startTime = performance.now();
5664
- }
5665
- this.#measured = false;
5666
- }
5667
- /**
5668
- * Measures the elapsed time since the last start time and sends the measurement data
5669
- * to the telemetry provider. Optionally marks the measurement as completed and/or
5670
- * resets the start time for subsequent measurements.
5671
- *
5672
- * @param data - Optional additional measurement data to merge with the existing data.
5673
- * @param options - Optional measurement options.
5674
- * @param options.markAsMeasured - If true, marks this measurement as completed.
5675
- * @param options.resetStartTime - If true, resets the start time after measuring.
5676
- * @returns The elapsed duration in milliseconds since the last start time.
5677
- */
5678
- measure(data, options) {
5679
- const measuredData = mergeTelemetryItem(this.#data, data ?? {});
5680
- const duration = performance.now() - this.#startTime;
5681
- this.#provider.trackMetric({
5682
- ...measuredData,
5683
- value: duration,
5684
- });
5685
- // Mark as measured only when explicitly requested by the caller
5686
- if (options?.markAsMeasured) {
5687
- this.#measured = true;
5688
- }
5689
- // Restart the clock only when explicitly requested by the caller
5690
- if (options?.resetStartTime) {
5691
- this.#startTime = performance.now();
5692
- }
5693
- return duration;
5694
- }
5695
- /**
5696
- * Resolves a given promise, measures the result using the provided options, and returns the resolved value.
5697
- *
5698
- * @template T - The type of the resolved value from the promise.
5699
- * @param promise - The promise to resolve.
5700
- * @param options - Optional configuration for resolving and measuring:
5701
- * - `data`: A value or a function that receives the resolved result and returns measurement data.
5702
- * - `options`: Additional options to pass to the `measure` method.
5703
- * @returns A promise that resolves to the value of the input promise.
5704
- */
5705
- async resolve(promise, options) {
5706
- const result = await promise;
5707
- const endData = typeof options?.data === 'function' ? options?.data(result) : options?.data;
5708
- this.measure(await Promise.resolve(endData), options?.options);
5709
- return result;
5710
- }
5711
- /**
5712
- * Executes the provided function and resolves its result using the `resolve` method.
5713
- * The function `fn` can return either a value of type `T` or a Promise of type `T`.
5714
- * Optionally, resolution options can be provided.
5715
- *
5716
- * @template T - The return type of the function to execute.
5717
- * @param fn - A function that returns a value or a Promise to be resolved.
5718
- * @param options - Optional resolution options to customize the resolve behavior.
5719
- * @returns A Promise that resolves to the result of the executed function.
5720
- */
5721
- exec(fn, options) {
5722
- return this.resolve(Promise.resolve(fn()), options);
5723
- }
5724
- /**
5725
- * Disposes of the measurement instance. If the measurement has not yet been taken,
5726
- * this method ensures that it is performed before disposal.
5727
- *
5728
- * This method is intended to be used with the ECMAScript `Symbol.dispose` protocol,
5729
- * allowing for automatic resource management when used in a `using` statement.
5730
- *
5731
- * @see https://github.com/tc39/proposal-explicit-resource-management
5732
- */
5733
- [Symbol.dispose]() {
5734
- // Ensure a measurement is always taken before the instance is discarded
5735
- if (!this.#measured) {
5736
- try {
5737
- this.measure();
5738
- }
5739
- catch {
5740
- // Silently handle errors during disposal to ensure cleanup always completes
5741
- // The measurement failure should not prevent proper resource disposal
5742
- }
5743
- }
5744
- }
5745
- }
5746
-
5747
- /**
5748
- * Represents a framework event with customizable details and behavior.
5749
- *
5750
- * The `FrameworkEvent` class implements the `IFrameworkEvent` interface and provides a way to create and manage events
5751
- * with custom details and behavior. It allows you to:
5444
+ * The `FrameworkEvent` class implements the `IFrameworkEvent` interface and provides a way to create and manage events
5445
+ * with custom details and behavior. It allows you to:
5752
5446
  *
5753
5447
  * - Specify the event type and initial event details
5754
5448
  * - Access the event details
@@ -5906,34 +5600,148 @@ class TelemetryEvent extends FrameworkEvent {
5906
5600
  }
5907
5601
 
5908
5602
  /**
5909
- * Relays one record to a parent provider under the child's routing policy.
5603
+ * Represents a telemetry measurement that tracks elapsed time and reports measurement data
5604
+ * to a telemetry provider. Supports cloning, resetting, and marking measurements as completed.
5910
5605
  *
5911
- * Shared by live provider relay and startup recovery, so both honour the same relay filter
5912
- * and exporter ownership.
5606
+ * @remarks
5607
+ * - The measurement starts timing upon instantiation.
5608
+ * - Provides methods to measure elapsed time, reset the timer, clone the measurement, and
5609
+ * automatically measure on disposal if not already measured.
5610
+ * - Can be used to wrap asynchronous operations and automatically record their duration.
5611
+ *
5612
+ * @example
5613
+ * ```typescript
5614
+ * const measurement = new Measurement(provider, { name: 'operation' });
5615
+ * // ... perform operation ...
5616
+ * measurement.measure();
5617
+ * ```
5913
5618
  *
5914
- * @param target - Parent provider.
5915
- * @param item - Record to relay.
5916
- * @param policy - Child exporter ownership and relay filter.
5917
- * @returns `true` when the parent accepted the record or the filter intentionally rejected it;
5918
- * `false` when the parent cannot honour ownership (a legacy parent and an owned record).
5919
- * @throws Rethrows relay filter and parent delivery errors for the caller's health reporting.
5619
+ * @public
5920
5620
  */
5921
- function relayRecord(target, item, policy) {
5922
- // Reject intentionally filtered records without changing export ownership.
5923
- if (policy.relay && !policy.relay(item)) {
5924
- return true;
5621
+ class Measurement {
5622
+ #provider;
5623
+ #data;
5624
+ #startTime;
5625
+ #measured;
5626
+ /**
5627
+ * Initializes a new instance of the Measurement class.
5628
+ *
5629
+ * @param provider - The telemetry provider responsible for handling telemetry data.
5630
+ * @param data - The measurement data to be associated with this instance.
5631
+ */
5632
+ constructor(provider, data) {
5633
+ this.#data = data;
5634
+ this.#provider = provider;
5635
+ this.#startTime = performance.now();
5925
5636
  }
5926
- // Ownership-aware parents suppress their fallback exporters for records a child owns.
5927
- if (target.trackRelayed) {
5928
- target.trackRelayed(item, policy.ownsExport);
5929
- return true;
5637
+ /**
5638
+ * Creates a new `Measurement` instance that is a copy of the current one.
5639
+ *
5640
+ * @param resetOptions - Options to control which properties are preserved in the clone.
5641
+ * - `preserveStartTime`: If `true`, the start time of the measurement is preserved in the clone.
5642
+ * @returns A new `Measurement` instance with copied data and optionally preserved start time.
5643
+ */
5644
+ clone(resetOptions = {}) {
5645
+ const clonedMeasurement = new Measurement(this.#provider, this.#data);
5646
+ // Preserve the original start time when requested, otherwise the clone starts fresh
5647
+ if (resetOptions.preserveStartTime) {
5648
+ clonedMeasurement.#startTime = this.#startTime;
5649
+ }
5650
+ return clonedMeasurement;
5930
5651
  }
5931
- // Legacy parents cannot honour ownership, so they only receive records no child owns.
5932
- if (!policy.ownsExport) {
5933
- target.track(item);
5934
- return true;
5652
+ /**
5653
+ * Resets the measurement state.
5654
+ *
5655
+ * @param options - Optional settings for the reset operation.
5656
+ * @param options.preserveStartTime - If true, the start time will not be reset; otherwise, it will be set to the current time.
5657
+ *
5658
+ * Resets the measured flag and, unless `preserveStartTime` is true, updates the start time to the current performance time.
5659
+ */
5660
+ reset(options) {
5661
+ // Keep the existing start time when preserving, otherwise restart the clock
5662
+ if (!options?.preserveStartTime) {
5663
+ this.#startTime = performance.now();
5664
+ }
5665
+ this.#measured = false;
5666
+ }
5667
+ /**
5668
+ * Measures the elapsed time since the last start time and sends the measurement data
5669
+ * to the telemetry provider. Optionally marks the measurement as completed and/or
5670
+ * resets the start time for subsequent measurements.
5671
+ *
5672
+ * @param data - Optional additional measurement data to merge with the existing data.
5673
+ * @param options - Optional measurement options.
5674
+ * @param options.markAsMeasured - If true, marks this measurement as completed.
5675
+ * @param options.resetStartTime - If true, resets the start time after measuring.
5676
+ * @returns The elapsed duration in milliseconds since the last start time.
5677
+ */
5678
+ measure(data, options) {
5679
+ const measuredData = mergeTelemetryItem(this.#data, data ?? {});
5680
+ const duration = performance.now() - this.#startTime;
5681
+ this.#provider.trackMetric({
5682
+ ...measuredData,
5683
+ value: duration,
5684
+ });
5685
+ // Mark as measured only when explicitly requested by the caller
5686
+ if (options?.markAsMeasured) {
5687
+ this.#measured = true;
5688
+ }
5689
+ // Restart the clock only when explicitly requested by the caller
5690
+ if (options?.resetStartTime) {
5691
+ this.#startTime = performance.now();
5692
+ }
5693
+ return duration;
5694
+ }
5695
+ /**
5696
+ * Resolves a given promise, measures the result using the provided options, and returns the resolved value.
5697
+ *
5698
+ * @template T - The type of the resolved value from the promise.
5699
+ * @param promise - The promise to resolve.
5700
+ * @param options - Optional configuration for resolving and measuring:
5701
+ * - `data`: A value or a function that receives the resolved result and returns measurement data.
5702
+ * - `options`: Additional options to pass to the `measure` method.
5703
+ * @returns A promise that resolves to the value of the input promise.
5704
+ */
5705
+ async resolve(promise, options) {
5706
+ const result = await promise;
5707
+ const endData = typeof options?.data === 'function' ? options?.data(result) : options?.data;
5708
+ this.measure(await Promise.resolve(endData), options?.options);
5709
+ return result;
5710
+ }
5711
+ /**
5712
+ * Executes the provided function and resolves its result using the `resolve` method.
5713
+ * The function `fn` can return either a value of type `T` or a Promise of type `T`.
5714
+ * Optionally, resolution options can be provided.
5715
+ *
5716
+ * @template T - The return type of the function to execute.
5717
+ * @param fn - A function that returns a value or a Promise to be resolved.
5718
+ * @param options - Optional resolution options to customize the resolve behavior.
5719
+ * @returns A Promise that resolves to the result of the executed function.
5720
+ */
5721
+ exec(fn, options) {
5722
+ return this.resolve(Promise.resolve(fn()), options);
5723
+ }
5724
+ /**
5725
+ * Disposes of the measurement instance. If the measurement has not yet been taken,
5726
+ * this method ensures that it is performed before disposal.
5727
+ *
5728
+ * This method is intended to be used with the ECMAScript `Symbol.dispose` protocol,
5729
+ * allowing for automatic resource management when used in a `using` statement.
5730
+ *
5731
+ * @see https://github.com/tc39/proposal-explicit-resource-management
5732
+ */
5733
+ [Symbol.dispose]() {
5734
+ // Ensure a measurement is always taken before the instance is discarded
5735
+ if (!this.#measured) {
5736
+ try {
5737
+ this.measure();
5738
+ }
5739
+ catch {
5740
+ // Silently handle errors during disposal to ensure cleanup always completes
5741
+ // The measurement failure should not prevent proper resource disposal
5742
+ }
5743
+ }
5935
5744
  }
5936
- return false;
5937
5745
  }
5938
5746
 
5939
5747
  /**
@@ -5976,656 +5784,80 @@ const applyMetadata = (metadata, args) => {
5976
5784
  // resolved metadata is always target, else item metadata would be overwritten
5977
5785
  map((data) => mergeTelemetryItem({ metadata: data }, args.item)), catchError((error) => {
5978
5786
  const errorItem = TelemetryExceptionSchema.parse({
5979
- type: TelemetryType.Exception,
5980
- name: TelemetryItemNames.MetadataError,
5981
- exception: error,
5982
- properties: {
5983
- sourceMetricName: args.item.name,
5984
- sourceMetricType: args.item.type,
5985
- },
5986
- });
5987
- return merge(of(errorItem), of(args.item));
5988
- }));
5989
- };
5990
-
5991
- /**
5992
- * Creates the reducer recording telemetry provider transitions.
5993
- * @returns A reducer with a fresh idle initial snapshot.
5994
- */
5995
- function createTelemetryProviderReducer() {
5996
- return createReducer$2(() => ({
5997
- status: 'idle',
5998
- enrichment: { pending: 0, builtIn: 0 },
5999
- processing: {},
6000
- failures: {},
6001
- }), (builder) => builder
6002
- .addCase(telemetryProviderActions.initialize, (state, { meta }) => {
6003
- state.initialization = meta.operation;
6004
- // Only an idle provider starts; disposal already in progress keeps its status.
6005
- if (state.status === 'idle') {
6006
- state.status = 'initializing';
6007
- }
6008
- })
6009
- .addCase(telemetryProviderActions.initialize.success, (state) => {
6010
- // Disposal requested during initialization wins over late readiness.
6011
- if (state.status === 'initializing') {
6012
- state.status = 'ready';
6013
- }
6014
- })
6015
- .addCase(telemetryProviderActions.dispose, (state, { meta }) => {
6016
- state.disposal = meta.operation;
6017
- state.status = 'disposing';
6018
- })
6019
- .addCase(telemetryProviderActions.close, (state) => {
6020
- state.status = 'disposed';
6021
- })
6022
- .addCase(telemetryProviderActions.enrichmentStarted, (state, { payload }) => {
6023
- state.enrichment.pending++;
6024
- // Only bounded built-in enrichment delays shutdown; custom enrichment is canceled.
6025
- if (payload.builtIn) {
6026
- state.enrichment.builtIn++;
6027
- }
6028
- })
6029
- .addCase(telemetryProviderActions.enrichmentSettled, (state, { payload }) => {
6030
- state.enrichment.pending--;
6031
- // Mirror the start transition so the shutdown drain condition stays exact.
6032
- if (payload.builtIn) {
6033
- state.enrichment.builtIn--;
6034
- }
6035
- })
6036
- .addCase(telemetryProviderActions.processingStarted, (state, { payload }) => {
6037
- state.processing[payload] = (state.processing[payload] ?? 0) + 1;
6038
- })
6039
- .addCase(telemetryProviderActions.processingSettled, (state, { payload }) => {
6040
- state.processing[payload] = (state.processing[payload] ?? 1) - 1;
6041
- })
6042
- .addCase(telemetryProviderActions.failed, (state, { payload }) => {
6043
- const failure = state.failures[payload.message];
6044
- // The first occurrence is retained for reporting; repeats only count.
6045
- if (failure) {
6046
- failure.count++;
6047
- }
6048
- else {
6049
- state.failures[payload.message] = { error: payload, count: 1 };
6050
- }
6051
- }));
6052
- }
6053
-
6054
- /**
6055
- * Emits once state satisfies a condition, immediately when it already does.
6056
- * @param state$ - Provider state stream.
6057
- * @param predicate - Condition over the snapshot.
6058
- * @returns A single emission of the first matching snapshot.
6059
- */
6060
- const whenState = (state$, predicate) =>
6061
- // Only the first matching snapshot gates the next step.
6062
- state$.pipe(filter(predicate), take(1));
6063
- /**
6064
- * Connects the provider once per initialize request and settles the shared operation.
6065
- * @param handlers - Engine work.
6066
- * @returns A flow emitting `initialize.success` or `initialize.failure`.
6067
- */
6068
- const initializeFlow = (handlers) => (action$) =>
6069
- // Repeated requests join the running operation, so ignore them while it is in flight.
6070
- action$.pipe(filter(telemetryProviderActions.initialize.match), exhaustMap(({ payload }) =>
6071
- // Connecting is the operation; its outcome settles every joined caller.
6072
- from(handlers.connect(payload)).pipe(map(() => telemetryProviderActions.initialize.success()), catchError((error) => of(telemetryProviderActions.initialize.failure(error))))));
6073
- /**
6074
- * Drains owned work in order, closes delivery and shuts down exporters once.
6075
- *
6076
- * Order: initialization settles → bounded built-in enrichment drains → delivery closes
6077
- * (canceling custom enrichment) → asynchronous adapter processing drains → repeated failures
6078
- * are summarized → exporters shut down.
6079
- *
6080
- * @param handlers - Engine work.
6081
- * @returns A flow emitting `close`, then `dispose.success` or `dispose.failure`.
6082
- */
6083
- const disposeFlow = (handlers) => (action$, state$) =>
6084
- // Disposal runs once; later requests join the same operation instead.
6085
- action$.pipe(filter(telemetryProviderActions.dispose.match), exhaustMap(() => {
6086
- // An in-flight initialization finishes before anything is torn down.
6087
- const initialized$ = whenState(state$, () => true).pipe(exhaustMap(({ initialization }) => from(Promise.allSettled([initialization]))), ignoreElements());
6088
- // Built-in enrichment has a deadline; retain terminal teardown records before closing.
6089
- const closed$ = whenState(state$, ({ enrichment }) => enrichment.builtIn === 0).pipe(tap(() => handlers.close()), map(() => telemetryProviderActions.close()));
6090
- // Accepted asynchronous processing is owned and must settle before exporters shut down.
6091
- const drained$ = whenState(state$, ({ processing }) =>
6092
- // Every adapter must be drained, not just the one that settled last.
6093
- Object.values(processing).every((pending) => !pending)).pipe(tap(({ failures }) => {
6094
- // Repeated failures stay visible without one event per failed record.
6095
- for (const [failure, { count }] of Object.entries(failures)) {
6096
- // A single occurrence was already reported; summarize only repeats.
6097
- if (count > 1) {
6098
- console.warn('Telemetry pipeline failure summary', { failure, count });
6099
- }
6100
- }
6101
- }), ignoreElements());
6102
- // Shutdown is attempted for every exporter; its outcome settles the operation.
6103
- const shutdown$ = defer(() => from(handlers.shutdown())).pipe(map(() => telemetryProviderActions.dispose.success()), catchError((error) => of(telemetryProviderActions.dispose.failure(error))));
6104
- return concat(initialized$, closed$, drained$, shutdown$);
6105
- }));
6106
- /**
6107
- * Reports each pipeline failure once, when it first appears in state.
6108
- * @param handlers - Engine work.
6109
- * @returns A flow that only performs reporting and emits no actions.
6110
- */
6111
- const failureFlow = (handlers) => (action$, state$) =>
6112
- // Pair each failure with the snapshot that already counted it.
6113
- action$.pipe(filter(telemetryProviderActions.failed.match), withLatestFrom(state$),
6114
- // Only the first occurrence is actionable; repeats are summarized at teardown.
6115
- filter(([{ payload }, { failures }]) => failures[payload.message]?.count === 1), tap(([{ payload }, { status }]) => handlers.report(payload, status)), ignoreElements());
6116
- /**
6117
- * Creates the flows driving a telemetry provider's operations and health reporting.
6118
- * @param handlers - Engine work the flows delegate to.
6119
- * @returns Flows to register on provider state.
6120
- */
6121
- function createTelemetryProviderFlows(handlers) {
6122
- return [initializeFlow(handlers), disposeFlow(handlers), failureFlow(handlers)];
6123
- }
6124
-
6125
- /** Pending metadata enrichment allowed per provider before built-in records skip enrichment. */
6126
- const METADATA_CAPACITY = 256;
6127
- /** Deadline for built-in enrichment; on expiry the original safe record is emitted. */
6128
- const BUILT_IN_METADATA_TIMEOUT_MS = 1_000;
6129
- /** Pending asynchronous processing allowed per adapter before records are dropped for it. */
6130
- const ADAPTER_PROCESSING_CAPACITY = 256;
6131
- /**
6132
- * The telemetry engine behind a `TelemetryProvider`.
6133
- *
6134
- * Owns the record pipeline (scope, built-in envelope protection, bounded metadata enrichment,
6135
- * adapter delivery, parent relay and event dispatch) and a reactive state of lifecycle,
6136
- * in-flight work and health. `initialize` and `dispose` are async actions whose flows perform
6137
- * the work; failures are recorded in state and reported once by a flow.
6138
- *
6139
- * Records stay on a synchronous stream rather than becoming actions, so adapter delivery
6140
- * happens within `track`. Operation outcomes are delivered as actions on the `asyncScheduler`,
6141
- * so with fake timers, advance timers to settle `initialize` or `dispose`.
6142
- *
6143
- * @example
6144
- * ```ts
6145
- * const state = new TelemetryProviderState({ config, owner: provider });
6146
- * await state.initialize({ parent });
6147
- * state.track(record);
6148
- * await state.dispose();
6149
- * ```
6150
- */
6151
- class TelemetryProviderState {
6152
- #subject;
6153
- #records = new Subject();
6154
- /** Delivery subscriptions, released together when delivery closes. */
6155
- #connections = new Subscription();
6156
- #config;
6157
- #context;
6158
- #modules;
6159
- /**
6160
- * Creates the engine and registers the flows driving its operations.
6161
- * @param context - Configuration, event bus and the public provider.
6162
- */
6163
- constructor({ config, ...context }) {
6164
- const adapters = config?.adapters ?? {};
6165
- // Configuration, not delivery success, determines inherited fallback suppression.
6166
- const ownsExport = config?.ownsExport ?? Object.values(adapters).some((adapter) => !adapter.fallback);
6167
- this.#config = Object.freeze({
6168
- ...config,
6169
- adapters,
6170
- defaultScope: config?.defaultScope ?? [],
6171
- ownsExport,
6172
- });
6173
- this.#context = context;
6174
- this.#subject = new FlowSubject(createTelemetryProviderReducer());
6175
- const flows = createTelemetryProviderFlows({
6176
- connect: (args) => this.#connect(args),
6177
- close: () => this.#close(),
6178
- shutdown: () => this.#shutdown(),
6179
- report: (error, status) => this.#report(error, status),
6180
- });
6181
- // Every operation and health reaction is a flow over this state's actions.
6182
- for (const flow of flows) {
6183
- this.#subject.addFlow(flow);
6184
- }
6185
- }
6186
- /**
6187
- * Current snapshot of lifecycle, in-flight work and health.
6188
- * @returns The latest reduced snapshot.
6189
- */
6190
- get value() {
6191
- return this.#subject.value;
6192
- }
6193
- /**
6194
- * Every record tracked by the provider, after scope and enrichment.
6195
- * @returns The record stream without its internal ownership envelope.
6196
- */
6197
- get items() {
6198
- // Expose records without their internal ownership envelope.
6199
- return this.#records.pipe(map(({ item }) => item));
6200
- }
6201
- /**
6202
- * Exporters configured for this provider, keyed by identifier.
6203
- * @returns The resolved adapters.
6204
- */
6205
- get adapters() {
6206
- return this.#config.adapters;
6207
- }
6208
- /**
6209
- * Sets the resolved framework module instances used to resolve metadata.
6210
- * @param value - The resolved framework module instances.
6211
- */
6212
- set modules(value) {
6213
- this.#modules = value;
6214
- }
6215
- /**
6216
- * Selects a derived value, emitting the current value and then only changes.
6217
- * @param selector - Projection of the snapshot.
6218
- * @returns The selected value stream.
6219
- * @template T - Selected value type.
6220
- */
6221
- select(selector) {
6222
- return this.#subject.select(selector);
6223
- }
6224
- /**
6225
- * Emits once when the snapshot satisfies a condition, immediately when it already does.
6226
- * @param predicate - Condition over the snapshot.
6227
- * @returns A single-emission stream suited to cancellation with `takeUntil`.
6228
- */
6229
- when(predicate) {
6230
- // Only the first matching transition matters for cancellation.
6231
- return this.select(predicate).pipe(filter((matched) => matched), take(1));
6232
- }
6233
- /**
6234
- * Connects exporters, the parent relay and startup streams once; repeated calls join.
6235
- * @param args - Optional parent provider and startup records.
6236
- * @returns Resolves when the provider is connected.
6237
- * @throws {Error} When the provider has already been disposed.
6238
- */
6239
- initialize(args) {
6240
- const { status, initialization } = this.value;
6241
- // A disposed provider must not reconnect exporters or event subscriptions.
6242
- if (status === 'disposed') {
6243
- return Promise.reject(new Error('Cannot initialize disposed telemetry'));
6244
- }
6245
- // Repeated and reentrant initialization joins the same operation.
6246
- if (initialization) {
6247
- return initialization;
6248
- }
6249
- const operation = this.#settled(telemetryProviderActions.initialize.success.match, telemetryProviderActions.initialize.failure.match);
6250
- this.#subject.next(telemetryProviderActions.initialize(args, operation));
6251
- return operation;
6252
- }
6253
- /**
6254
- * Drains owned work, closes delivery and shuts down exporters once; repeated calls join.
6255
- * @returns Resolves after every exporter has shut down.
6256
- * @throws {AggregateError} When any exporter fails to shut down; all are still attempted.
6257
- */
6258
- dispose() {
6259
- const { disposal } = this.value;
6260
- // Direct disposal and module disposal must join the same pending shutdown.
6261
- if (disposal) {
6262
- return disposal;
6263
- }
6264
- const operation = this.#settled(telemetryProviderActions.dispose.success.match, telemetryProviderActions.dispose.failure.match);
6265
- // Dispatch before any exporter callback runs, so reentrant disposal joins this operation.
6266
- this.#subject.next(telemetryProviderActions.dispose(operation));
6267
- return operation;
6268
- }
6269
- /**
6270
- * Records a pipeline health failure; the first occurrence of each is reported.
6271
- * @param error - Controlled failure description; never a raw business error.
6272
- */
6273
- fail(error) {
6274
- this.#subject.next(telemetryProviderActions.failed(error));
6275
- }
6276
- /**
6277
- * Tracks a validated record: applies scope, protects built-in envelopes, enriches metadata
6278
- * within bounds and emits it to adapters, the parent and the event bus.
6279
- *
6280
- * @param item - Validated telemetry record.
6281
- * @param handledByChild - Whether an explicit child exporter already handled the record.
6282
- */
6283
- track(item, handledByChild = false) {
6284
- const { status, enrichment } = this.value;
6285
- // Stop accepting records before flushing, while allowing already-owned enrichment to settle.
6286
- if (status === 'disposing' || status === 'disposed') {
6287
- return;
6288
- }
6289
- const mergedItem = mergeTelemetryItem(item, {
6290
- scope: item.diagnostic ? undefined : this.#config.defaultScope,
6291
- });
6292
- // Snapshot before invoking consumer metadata; neither enrichment nor adapters may rewrite origin.
6293
- const diagnostic = mergedItem.diagnostic
6294
- ? Object.freeze({ ...mergedItem.diagnostic })
6295
- : undefined;
6296
- const operational = diagnostic
6297
- ? {
6298
- ...mergedItem,
6299
- diagnostic,
6300
- metadata: undefined,
6301
- scope: [...mergedItem.scope],
6302
- properties: mergedItem.properties ? { ...mergedItem.properties } : undefined,
6303
- }
6304
- : undefined;
6305
- // Immutable built-in envelopes keep one adapter from contaminating sibling or parent delivery.
6306
- if (operational) {
6307
- Object.freeze(operational.scope);
6308
- Object.freeze(operational.properties);
6309
- Object.freeze(operational);
6310
- }
6311
- const emit = (nextItem) => {
6312
- // Late custom metadata emissions cannot revive a completed provider.
6313
- if (this.value.status !== 'disposed') {
6314
- // Dynamic metadata is consumer-controlled; built-in records export only their safe envelope.
6315
- this.#records.next({ item: operational ?? nextItem, handledByChild });
6316
- }
6317
- };
6318
- const { metadata } = this.#config;
6319
- // Skip metadata application for TelemetryMetadataError items to prevent infinite recursion
6320
- if (!metadata || mergedItem.name === TelemetryItemNames.MetadataError) {
6321
- // Providers without metadata still need the same fallback precedence.
6322
- emit(mergedItem);
6323
- return;
6324
- }
6325
- // Bounded built-in enrichment degrades to the safe original instead of dropping the outcome.
6326
- if (diagnostic && enrichment.pending >= METADATA_CAPACITY) {
6327
- this.fail(new Error('Built-in telemetry metadata capacity exceeded'));
6328
- emit(mergedItem);
6329
- return;
6330
- }
6331
- const builtIn = diagnostic !== undefined;
6332
- this.#subject.next(telemetryProviderActions.enrichmentStarted(builtIn));
6333
- const metadata$ = defer(() => applyMetadata(metadata, { modules: this.#modules, item: mergedItem }));
6334
- // Enrichment is a bounded, cancellable stage whose in-flight work is provider state.
6335
- metadata$
6336
- .pipe(
6337
- // Custom records retain their legacy timing; built-in outcomes have a finite enrichment deadline.
6338
- builtIn ? timeout({ first: BUILT_IN_METADATA_TIMEOUT_MS }) : (source$) => source$,
6339
- // Failure recovery preserves the terminal outcome even when metadata throws or times out.
6340
- catchError(() => {
6341
- this.fail(new Error('Telemetry metadata resolution failed'));
6342
- return of(mergedItem);
6343
- }),
6344
- // Closing cancels custom enrichment; built-in enrichment has already drained by then.
6345
- takeUntil(this.when((snapshot) => snapshot.status === 'disposed')),
6346
- // Leaving flight is state, so disposal can wait on the drained condition.
6347
- finalize$1(() => this.#subject.next(telemetryProviderActions.enrichmentSettled(builtIn))))
6348
- .subscribe({
6349
- next: (nextItem) => {
6350
- // Raw enrichment errors are health failures, not exportable built-in exceptions.
6351
- if (diagnostic && nextItem.name === TelemetryItemNames.MetadataError) {
6352
- this.fail(new Error('Telemetry metadata resolution failed'));
6353
- return;
6354
- }
6355
- emit(nextItem);
6356
- },
6357
- error: () => {
6358
- this.fail(new Error('Telemetry metadata resolution failed'));
6359
- emit(mergedItem);
6360
- },
6361
- });
6362
- }
6363
- /**
6364
- * Settles an operation from its outcome action.
6365
- * @param succeeded - Matches the operation's success action.
6366
- * @param failed - Matches the operation's failure action.
6367
- * @returns Resolves on success; rejects with the failure payload.
6368
- * @throws The failure action's payload, normally the original operation error.
6369
- */
6370
- #settled(succeeded, failed) {
6371
- // The first outcome of this operation settles every joined caller.
6372
- const outcome$ = this.#subject.action$.pipe(filter((action) => succeeded(action) || failed(action)));
6373
- return firstValueFrom(outcome$).then((action) => {
6374
- // A failure outcome carries the original error as its payload.
6375
- if (failed(action)) {
6376
- throw 'payload' in action ? action.payload : action;
6377
- }
6378
- });
6379
- }
6380
- /**
6381
- * Initializes exporters, then connects delivery, the parent relay and startup streams.
6382
- * @param args - Optional parent provider and startup records.
6383
- */
6384
- async #connect(args) {
6385
- const { parent, initialItems } = args ?? {};
6386
- const exporters = Object.entries(this.#config.adapters);
6387
- // Exporters initialize concurrently; one failing exporter must not block the others.
6388
- const results = await Promise.allSettled(
6389
- // Start every exporter's initialization; async wrapping turns synchronous throws into rejections.
6390
- exporters.map(async ([, adapter]) => adapter.initialize()));
6391
- // Report each exporter that failed to initialize, without its raw error.
6392
- for (const [index, result] of results.entries()) {
6393
- // Fulfilled exporters need no action.
6394
- if (result.status === 'rejected') {
6395
- this.fail(new Error(`Failed to initialize telemetry adapter "${exporters[index][0]}"`));
6396
- }
6397
- }
6398
- // Disposal may have closed delivery while an asynchronous exporter initialized.
6399
- if (this.value.status === 'disposed') {
6400
- return;
6401
- }
6402
- this.#connections.add(this.#records.subscribe((record) => this.#deliver(record)));
6403
- // Only a configured parent receives relayed records.
6404
- if (parent) {
6405
- this.#connections.add(this.#records.subscribe((record) => this.#relay(parent, record)));
6406
- }
6407
- this.#connections.add(this.#records.subscribe(({ item }) => this.#dispatchEvent(item)));
6408
- // Keep subscriptions independent so a failed custom stream cannot terminate host lifecycle capture.
6409
- // A disabled capture stays attached but unconsumed, so the host can still recover it on failure.
6410
- if (this.#config.lifecycleItems$ && this.#config.lifecycleItemsEnabled !== false) {
6411
- // The provider is the authority on host identity once it exists.
6412
- this.#connections.add(this.#track(from(this.#config.lifecycleItems$).pipe(map((item) => this.#stamp(item)))));
6413
- }
6414
- // Startup records passed at initialization are tracked as given.
6415
- if (initialItems) {
6416
- this.#connections.add(this.#track(initialItems));
6417
- }
6418
- }
6419
- /**
6420
- * Stamps this provider's host identity on a built-in lifecycle record, so records captured
6421
- * before the provider existed share its source and host ID.
6422
- * @param item - Captured lifecycle record.
6423
- * @returns The record carrying this provider's identity and scope.
6424
- */
6425
- #stamp(item) {
6426
- const source = this.#context.owner.source;
6427
- // Custom records and providers without identity are left as captured.
6428
- if (!item.diagnostic || !source) {
6429
- return item;
6430
- }
6431
- const { source: kind, hostId, hostName, hostVersion } = source;
6432
- return {
6433
- ...item,
6434
- scope: operationalScope(source),
6435
- diagnostic: { ...item.diagnostic, source: kind, hostId, hostName, hostVersion },
6436
- };
6437
- }
6438
- /**
6439
- * Tracks a startup stream through the public provider so each record is validated.
6440
- * @param items - Startup records.
6441
- * @returns The owned subscription.
6442
- */
6443
- #track(items) {
6444
- return from(items).subscribe({
6445
- next: (item) => this.#context.owner.track(item),
6446
- error: () => this.fail(new Error('Initial telemetry stream failed')),
6447
- });
6448
- }
6449
- /**
6450
- * Delivers one record to every eligible exporter within per-exporter capacity.
6451
- * @param record - Record and its fallback ownership.
6452
- */
6453
- #deliver({ item, handledByChild }) {
6454
- const { adapter: adapterFilter } = this.#config.filter ?? {};
6455
- try {
6456
- // A host-level adapter filter intentionally rejects delivery without enabling fallback.
6457
- if (adapterFilter && !adapterFilter(item)) {
6458
- return;
6459
- }
6460
- }
6461
- catch {
6462
- this.fail(new Error('Telemetry adapter filter failed'));
6463
- return;
6464
- }
6465
- // Every exporter is attempted independently.
6466
- for (const [identifier, adapter] of Object.entries(this.#config.adapters)) {
6467
- // Explicit child exporters take precedence over inherited development exporters.
6468
- if (adapter.fallback && handledByChild) {
6469
- // Keep application-owned records out of parent fallback exporters.
6470
- continue;
6471
- }
6472
- // A slow exporter cannot consume a healthy sibling exporter's processing capacity.
6473
- if ((this.value.processing[identifier] ?? 0) >= ADAPTER_PROCESSING_CAPACITY) {
6474
- this.fail(new Error('Telemetry adapter processing capacity exceeded'));
6475
- // Keep attempting sibling adapters even when this destination is saturated.
6476
- continue;
6477
- }
6478
- try {
6479
- const result = adapter.processItem(item);
6480
- // Only asynchronous processing needs shutdown ownership and bounded pending capacity.
6481
- if (result) {
6482
- this.#subject.next(telemetryProviderActions.processingStarted(identifier));
6483
- Promise.resolve(result)
6484
- .catch(() => this.fail(new Error(`Failed to process telemetry item with adapter "${identifier}"`)))
6485
- .finally(() => this.#subject.next(telemetryProviderActions.processingSettled(identifier)));
6486
- }
6487
- }
6488
- catch {
6489
- this.fail(new Error(`Failed to process telemetry item with adapter "${identifier}"`));
6490
- }
6491
- }
6492
- }
6493
- /**
6494
- * Relays one record to the parent, carrying exporter ownership.
6495
- * @param target - Parent provider.
6496
- * @param record - Record and its fallback ownership.
6497
- */
6498
- #relay(target, { item, handledByChild }) {
6499
- const policy = {
6500
- // Carry exporter ownership across intermediate providers without changing record metadata.
6501
- ownsExport: handledByChild || this.#config.ownsExport,
6502
- relay: this.#config.filter?.relay,
6503
- };
6504
- try {
6505
- // Legacy parents predate ownership; live relay keeps sending them every record.
6506
- if (!relayRecord(target, item, policy)) {
6507
- target.track(item);
6508
- }
6509
- }
6510
- catch {
6511
- this.fail(new Error('Telemetry parent relay failed'));
6512
- }
6513
- }
6514
- /**
6515
- * Dispatches one record as an event when an event bus is configured.
6516
- * @param item - Delivered record.
6517
- */
6518
- #dispatchEvent(item) {
6519
- const { event, owner } = this.#context;
6520
- // Only dispatch when an event provider is configured
6521
- if (!event) {
6522
- return;
6523
- }
6524
- try {
6525
- Promise.resolve(event.dispatchEvent(new TelemetryEvent(item, owner))).catch(() => this.fail(new Error('Telemetry event dispatch failed')));
6526
- }
6527
- catch {
6528
- this.fail(new Error('Telemetry event dispatch failed'));
6529
- }
6530
- }
6531
- /** Releases delivery subscriptions and completes the record stream. */
6532
- #close() {
6533
- this.#connections.unsubscribe();
6534
- this.#records.complete();
6535
- }
6536
- /**
6537
- * Shuts down every exporter, even when one rejects or throws synchronously.
6538
- * @throws {AggregateError} When any exporter fails to shut down.
6539
- */
6540
- async #shutdown() {
6541
- // Attempt every exporter; one failure must not skip the others.
6542
- const shutdowns = Object.entries(this.#config.adapters).map(async ([identifier, adapter]) => {
6543
- try {
6544
- await adapter.shutdown?.();
6545
- }
6546
- catch (cause) {
6547
- throw new Error(`Failed to shut down telemetry adapter "${identifier}"`, { cause });
6548
- }
6549
- });
6550
- const results = await Promise.allSettled(shutdowns);
6551
- // Keep only failures for the module lifecycle's structured disposal error event.
6552
- const errors = results.flatMap((result) =>
6553
- // Successful shutdowns must not obscure another exporter's failure.
6554
- result.status === 'rejected' ? [result.reason] : []);
6555
- // Reject only after every exporter had a chance to release its resources.
6556
- if (errors.length) {
6557
- throw new AggregateError(errors, 'Failed to shut down telemetry adapters');
6558
- }
6559
- }
6560
- /**
6561
- * Reports a newly observed failure through the out-of-band health channel.
6562
- * @param error - First occurrence of the failure.
6563
- * @param status - Provider status when the failure was recorded.
6564
- */
6565
- #report(error, status) {
6566
- const { event, owner } = this.#context;
6567
- // The event bus is an out-of-band health sink, never an automatic telemetry exporter.
6568
- if (event && status !== 'disposed') {
6569
- try {
6570
- Promise.resolve(event.dispatchEvent(new TelemetryErrorEvent(error, owner))).catch(() => console.warn('Telemetry health event delivery failed'));
6571
- }
6572
- catch {
6573
- // A failed health channel must remain visible without recursively dispatching another event.
6574
- console.warn('Telemetry health event delivery failed');
6575
- }
6576
- }
6577
- else {
6578
- console.warn(error.message);
6579
- }
6580
- }
6581
- }
5787
+ type: TelemetryType.Exception,
5788
+ name: TelemetryItemNames.MetadataError,
5789
+ exception: error,
5790
+ properties: {
5791
+ sourceMetricName: args.item.name,
5792
+ sourceMetricType: args.item.type,
5793
+ },
5794
+ });
5795
+ return merge(of(errorItem), of(args.item));
5796
+ }));
5797
+ };
6582
5798
 
6583
5799
  /**
6584
- * The telemetry provider applications and modules use to track events, exceptions, metrics
6585
- * and custom telemetry.
5800
+ * Provides telemetry tracking, event dispatching, and adapter integration for application instrumentation.
5801
+ *
5802
+ * The `TelemetryProvider` class is responsible for collecting, processing, and relaying telemetry data such as events,
5803
+ * exceptions, metrics, and custom events. It supports adapter-based processing, event provider integration, and
5804
+ * hierarchical telemetry relaying to parent providers. Telemetry items are validated and dispatched to registered
5805
+ * adapters and event providers, with robust error handling and metadata merging.
6586
5806
  *
6587
- * `TelemetryProvider` is a thin, consumer-facing facade: it validates input against the item
6588
- * schemas and delegates everything else (scope, enrichment, exporter delivery, parent relay,
6589
- * lifecycle and health) to its {@link TelemetryProviderState} engine.
5807
+ * @remarks
5808
+ * - Integrates with adapters for extensible telemetry processing.
5809
+ * - Supports event provider for dispatching telemetry as events.
5810
+ * - Allows relaying telemetry data to a parent provider, merging metadata as needed.
5811
+ * - Provides utility methods for tracking and measuring telemetry items.
5812
+ * - Handles error reporting via telemetry error events.
6590
5813
  *
6591
5814
  * @example
6592
5815
  * ```typescript
6593
5816
  * const provider = new TelemetryProvider(config, { event: eventProvider });
6594
- * await provider.initialize({ parent });
6595
- * provider.trackEvent({ name: 'user_login', metadata: { userId: '123' } });
5817
+ * provider.track({ type: TelemetryType.Event, name: 'user_login', metadata: { userId: '123' } });
6596
5818
  * const end = provider.measure({ name: 'load_time' });
6597
5819
  * // ... do work ...
6598
5820
  * end();
6599
- * await provider.dispose();
6600
5821
  * ```
5822
+ *
5823
+ * @typeParam TelemetryConfig - The configuration type for telemetry.
5824
+ * @implements ITelemetryProvider
5825
+ * @extends BaseModuleProvider<TelemetryConfig>
6601
5826
  */
6602
5827
  class TelemetryProvider extends BaseModuleProvider {
6603
- /** Immutable composition-root origin used by optional infrastructure reporters. */
6604
- source;
6605
- #state;
5828
+ #items = new Subject();
5829
+ #initialized = false;
5830
+ #disposal;
5831
+ #adapters = {};
5832
+ #handledByChild = new WeakSet();
5833
+ #filters;
5834
+ #defaultScope;
5835
+ #eventProvider;
6606
5836
  /**
6607
5837
  * Stream of every telemetry item tracked by this provider.
6608
5838
  *
6609
5839
  * @returns An observable emitting each tracked `TelemetryItem`.
6610
5840
  */
6611
5841
  get items() {
6612
- return this.#state.items;
5842
+ return this.#items.asObservable();
6613
5843
  }
6614
5844
  /**
6615
- * Returns true once initialization has started.
5845
+ * Returns true if the provider has been initialized.
6616
5846
  *
6617
- * @returns `true` once {@link initialize} has been called, otherwise `false`.
5847
+ * @returns `true` when {@link initialize} has completed, otherwise `false`.
6618
5848
  */
6619
5849
  get initialized() {
6620
- return this.#state.value.status !== 'idle';
5850
+ return this.#initialized;
6621
5851
  }
5852
+ #metadata;
5853
+ #modules;
6622
5854
  /**
6623
5855
  * Sets the resolved framework module instances used to resolve metadata.
6624
5856
  *
6625
5857
  * @param value - The resolved framework module instances.
6626
5858
  */
6627
5859
  set modules(value) {
6628
- this.#state.modules = value;
5860
+ this.#modules = value;
6629
5861
  }
6630
5862
  /**
6631
5863
  * Creates a new `TelemetryProvider`.
@@ -6636,27 +5868,51 @@ class TelemetryProvider extends BaseModuleProvider {
6636
5868
  */
6637
5869
  constructor(config, deps) {
6638
5870
  super({ version: version$2, config });
6639
- this.source = Object.freeze(config?.source ?? { hostId: crypto.randomUUID() });
6640
- this.#state = new TelemetryProviderState({ config, event: deps?.event, owner: this });
5871
+ this.#adapters = config?.adapters ?? {};
5872
+ this.#metadata = config?.metadata;
5873
+ this.#defaultScope = config?.defaultScope ?? [];
5874
+ this.#eventProvider = deps?.event;
5875
+ this.#filters = config?.filter;
6641
5876
  }
6642
5877
  /**
6643
- * Connects exporters, the parent relay and startup streams. Exporter initialization failures
6644
- * are reported but do not prevent the provider from becoming usable.
5878
+ * Initializes the telemetry provider with adapters, parent provider, and optional initial items.
6645
5879
  *
6646
- * Repeated and reentrant calls return the same promise.
5880
+ * This method sets up the provider for operation by:
5881
+ * 1. Storing the provided adapters
5882
+ * 2. Initializing all adapters that support async initialization (failures are logged but don't prevent initialization)
5883
+ * 3. Setting up subscriptions for telemetry processing
5884
+ * 4. Handling initial telemetry items if provided
6647
5885
  *
6648
- * @param args - Optional parent provider to relay to, and startup records to track.
6649
- * @returns Resolves when the provider is connected.
6650
- * @throws {Error} When the provider has already been disposed.
5886
+ * @param args - Initialization arguments
5887
+ * @param args.adapters - Array of telemetry adapters to use
5888
+ * @param args.parent - Optional parent telemetry provider for relaying data
5889
+ * @param args.initialItems - Optional observable of initial telemetry items to process
5890
+ * @returns A promise that resolves when initialization is complete
6651
5891
  */
6652
- initialize(args) {
6653
- return this.#state.initialize(args);
6654
- }
6655
- /** {@inheritDoc ITelemetryProvider.scope} */
6656
- scope(module, options) {
6657
- const scope = createOperationalTelemetry(module, options);
6658
- scope.connect(this);
6659
- return scope;
5892
+ async initialize(args) {
5893
+ const { parent, initialItems } = args ?? {};
5894
+ // Mark as initialized early to allow adapter connections
5895
+ this.#initialized = true;
5896
+ // Initialize all adapters that support async initialization
5897
+ await this._initializeAdapters();
5898
+ // Connect adapters to the telemetry items, processing each item as it is emitted
5899
+ this._addTeardown(this._connectAdapters());
5900
+ // If a parent telemetry provider is provided, relay telemetry data to it
5901
+ if (parent) {
5902
+ this._addTeardown(this._relayTelemetryData(parent));
5903
+ }
5904
+ // Emit telemetry items as events if an event provider is available
5905
+ this._addTeardown(this.#items.subscribe(this._dispatchEntityEvent.bind(this)));
5906
+ // Handle initial telemetry items if provided
5907
+ if (initialItems) {
5908
+ this._addTeardown(from(initialItems).subscribe((item) => {
5909
+ this.track(item);
5910
+ }));
5911
+ }
5912
+ // Ensure the subject is completed when the provider is destroyed
5913
+ this._addTeardown(() => {
5914
+ this.#items.complete();
5915
+ });
6660
5916
  }
6661
5917
  /**
6662
5918
  * Stops telemetry subscriptions and waits for every adapter to flush and shut down.
@@ -6672,7 +5928,158 @@ class TelemetryProvider extends BaseModuleProvider {
6672
5928
  * ```
6673
5929
  */
6674
5930
  dispose() {
6675
- return this.#state.dispose();
5931
+ // Direct disposal and module disposal must join the same pending shutdown.
5932
+ if (this.#disposal) {
5933
+ return this.#disposal;
5934
+ }
5935
+ super.dispose();
5936
+ // Defer adapter callbacks until the shared promise is assigned, including reentrant disposal.
5937
+ this.#disposal = Promise.resolve().then(async () => {
5938
+ // Shut down all exporters even when one rejects or throws synchronously.
5939
+ const results = await Promise.allSettled(Object.entries(this.#adapters).map(async ([identifier, adapter]) => {
5940
+ try {
5941
+ await adapter.shutdown?.();
5942
+ }
5943
+ catch (cause) {
5944
+ throw new Error(`Failed to shut down telemetry adapter "${identifier}"`, { cause });
5945
+ }
5946
+ }));
5947
+ const errors = [];
5948
+ // Retain each failure for the module lifecycle's structured disposal error event.
5949
+ for (const result of results) {
5950
+ // Successful shutdowns must not obscure another exporter's failure.
5951
+ if (result.status === 'rejected') {
5952
+ errors.push(result.reason);
5953
+ }
5954
+ }
5955
+ // Reject only after all exporters have had a chance to release their resources.
5956
+ if (errors.length) {
5957
+ throw new AggregateError(errors, 'Failed to shut down telemetry adapters');
5958
+ }
5959
+ });
5960
+ return this.#disposal;
5961
+ }
5962
+ /**
5963
+ * Initializes all telemetry adapters that support async initialization.
5964
+ * Uses Promise.allSettled to ensure all adapters attempt initialization,
5965
+ * with individual error reporting for any failures.
5966
+ * Subclasses can override this method to customize adapter initialization behavior.
5967
+ *
5968
+ * @protected
5969
+ */
5970
+ async _initializeAdapters() {
5971
+ // Initialize all adapters, do it in parallel
5972
+ const adapterEntries = Object.entries(this.#adapters);
5973
+ // Kick off every adapter's initialize() concurrently
5974
+ const initializationPromises = adapterEntries.map(([_identifier, adapter]) => adapter.initialize());
5975
+ // Wait for all adapters to settle (either resolve or reject)
5976
+ const results = await Promise.allSettled(initializationPromises);
5977
+ // Check each result and dispatch errors for failed initializations
5978
+ for (const [index, result] of results.entries()) {
5979
+ // Only rejected adapters need an error dispatched; fulfilled ones need no action
5980
+ if (result.status === 'rejected') {
5981
+ const [identifier] = adapterEntries[index];
5982
+ this._dispatchError(new Error(`Failed to initialize telemetry adapter "${identifier}"`, {
5983
+ cause: result.reason,
5984
+ }));
5985
+ }
5986
+ }
5987
+ }
5988
+ /**
5989
+ * Subscribes all adapters to the telemetry item stream.
5990
+ * Each adapter processes every telemetry item as it is emitted.
5991
+ * If an adapter filter is configured, only items that pass the filter will be sent to adapters.
5992
+ * If an adapter throws, the error is dispatched as a telemetry error event.
5993
+ *
5994
+ * @returns Subscription to the telemetry item stream
5995
+ * @protected
5996
+ * @throws {Error} When the provider has not been initialized yet.
5997
+ */
5998
+ _connectAdapters() {
5999
+ // Adapters can only be connected after the provider has completed initialization
6000
+ if (!this.#initialized) {
6001
+ throw new Error('TelemetryProvider is not initialized');
6002
+ }
6003
+ return this.#items.subscribe((item) => {
6004
+ const { adapter: adapterFilter } = this.#filters ?? {};
6005
+ // Apply adapter filter if configured - skip item if filter returns false
6006
+ if (adapterFilter && !adapterFilter(item)) {
6007
+ return;
6008
+ }
6009
+ // Iterate through all registered adapters
6010
+ for (const [identifier, adapter] of Object.entries(this.#adapters)) {
6011
+ // Explicit child exporters take precedence over inherited development exporters.
6012
+ if (adapter.fallback && this.#handledByChild.has(item)) {
6013
+ // Keep application-owned records out of parent fallback exporters.
6014
+ continue;
6015
+ }
6016
+ try {
6017
+ // Let the adapter process the telemetry item
6018
+ Promise.resolve(adapter.processItem(item));
6019
+ }
6020
+ catch (error) {
6021
+ // If processing fails, dispatch an error event
6022
+ this._dispatchError(new Error(`Failed to process telemetry item with adapter "${identifier}"`, {
6023
+ cause: error,
6024
+ }));
6025
+ }
6026
+ }
6027
+ });
6028
+ }
6029
+ /**
6030
+ * Relays telemetry data items to a target telemetry provider.
6031
+ *
6032
+ * Subscribes to the internal telemetry item stream and forwards each processed item
6033
+ * to the specified target provider by invoking its `track` method.
6034
+ * If a relay filter is configured, only items that pass the filter will be relayed.
6035
+ *
6036
+ * @param target - The telemetry provider to which telemetry data should be relayed.
6037
+ * @returns A Subscription object that can be used to unsubscribe from the relay.
6038
+ */
6039
+ _relayTelemetryData(target) {
6040
+ // Subscribe to the processed stream and forward each item to the parent provider
6041
+ return this.#items.subscribe((item) => {
6042
+ const { relay: relayFilter } = this.#filters ?? {};
6043
+ // Apply relay filter if configured - skip item if filter returns false
6044
+ if (relayFilter && !relayFilter(item)) {
6045
+ return;
6046
+ }
6047
+ // Older/custom providers retain the original track-only relay contract.
6048
+ if (target.trackRelayed) {
6049
+ // Carry exporter ownership across intermediate providers without changing record metadata.
6050
+ const hasAdapters = this.#handledByChild.has(item) ||
6051
+ Object.values(this.#adapters).some((adapter) => !adapter.fallback);
6052
+ target.trackRelayed(item, hasAdapters);
6053
+ }
6054
+ else {
6055
+ target.track(item);
6056
+ }
6057
+ });
6058
+ }
6059
+ /**
6060
+ * Dispatches a telemetry event to the event provider, if available.
6061
+ *
6062
+ * @param item - The telemetry item to dispatch as an event
6063
+ * @protected
6064
+ */
6065
+ _dispatchEntityEvent(item) {
6066
+ // Only dispatch when an event provider is configured
6067
+ if (this.#eventProvider) {
6068
+ // Wrap the telemetry item in a TelemetryEvent and dispatch it
6069
+ this.#eventProvider.dispatchEvent(new TelemetryEvent(item, this));
6070
+ }
6071
+ }
6072
+ /**
6073
+ * Dispatches a telemetry error event using the internal event provider, if available.
6074
+ *
6075
+ * @param error - The error instance to be dispatched with the telemetry error event.
6076
+ * @protected
6077
+ */
6078
+ _dispatchError(error) {
6079
+ // Only dispatch when an event provider is configured
6080
+ if (this.#eventProvider) {
6081
+ this.#eventProvider.dispatchEvent(new TelemetryErrorEvent(error, this));
6082
+ }
6676
6083
  }
6677
6084
  /**
6678
6085
  * Tracks a telemetry item by type, merging the default scope with the provided scope.
@@ -6710,7 +6117,44 @@ class TelemetryProvider extends BaseModuleProvider {
6710
6117
  * @param hasAdapters - Whether an explicit child adapter has already handled this record.
6711
6118
  */
6712
6119
  trackRelayed(item, hasAdapters) {
6713
- this.#state.track(item, hasAdapters);
6120
+ this._track(item, hasAdapters);
6121
+ }
6122
+ /**
6123
+ * Tracks a telemetry item by merging the default scope with the item's scope (if provided),
6124
+ * ensuring that all telemetry items have the correct scope context. The merged item is then
6125
+ * emitted to downstream handlers.
6126
+ *
6127
+ * @param item - The telemetry item to be tracked, conforming to the input schema of `TelemetryItemSchema`.
6128
+ * @param handledByChild - Whether fallback adapters should skip this relayed item.
6129
+ *
6130
+ * @protected
6131
+ */
6132
+ async _track(item, handledByChild = false) {
6133
+ const mergedItem = mergeTelemetryItem(item, {
6134
+ scope: this.#defaultScope,
6135
+ });
6136
+ // Skip metadata application for TelemetryMetadataError items to prevent infinite recursion
6137
+ if (this.#metadata && mergedItem.name !== TelemetryItemNames.MetadataError) {
6138
+ applyMetadata(this.#metadata, {
6139
+ modules: this.#modules,
6140
+ item: mergedItem,
6141
+ }).subscribe((nextItem) => {
6142
+ // Async metadata resolution creates new objects; retain ownership on the emitted record.
6143
+ if (handledByChild) {
6144
+ this.#handledByChild.add(nextItem);
6145
+ }
6146
+ // Emit the item after applying metadata
6147
+ this.#items.next(nextItem);
6148
+ });
6149
+ }
6150
+ else {
6151
+ // Providers without metadata still need the same fallback precedence.
6152
+ if (handledByChild) {
6153
+ this.#handledByChild.add(mergedItem);
6154
+ }
6155
+ // If no metadata extractor is provided or item is TelemetryMetadataError, emit directly
6156
+ this.#items.next(mergedItem);
6157
+ }
6714
6158
  }
6715
6159
  /**
6716
6160
  * Starts a metric measurement and returns a function to end the measurement.
@@ -6736,7 +6180,7 @@ class TelemetryProvider extends BaseModuleProvider {
6736
6180
  * provider.trackEvent({ name: 'button_click', metadata: { button: 'save' } });
6737
6181
  */
6738
6182
  trackEvent(data) {
6739
- this.#state.track(TelemetryEventSchema.parse({ ...data, type: TelemetryType.Event }));
6183
+ this._track(TelemetryEventSchema.parse({ ...data, type: TelemetryType.Event }));
6740
6184
  }
6741
6185
  /**
6742
6186
  * Tracks a telemetry exception.
@@ -6750,7 +6194,7 @@ class TelemetryProvider extends BaseModuleProvider {
6750
6194
  * });
6751
6195
  */
6752
6196
  trackException(data) {
6753
- this.#state.track(TelemetryExceptionSchema.parse({ ...data, type: TelemetryType.Exception }));
6197
+ this._track(TelemetryExceptionSchema.parse({ ...data, type: TelemetryType.Exception }));
6754
6198
  }
6755
6199
  /**
6756
6200
  * Tracks a telemetry metric.
@@ -6760,7 +6204,7 @@ class TelemetryProvider extends BaseModuleProvider {
6760
6204
  * provider.trackMetric({ name: 'response_time', value: 123 });
6761
6205
  */
6762
6206
  trackMetric(data) {
6763
- this.#state.track(TelemetryMetricSchema.parse({ ...data, type: TelemetryType.Metric }));
6207
+ this._track(TelemetryMetricSchema.parse({ ...data, type: TelemetryType.Metric }));
6764
6208
  }
6765
6209
  /**
6766
6210
  * Tracks a custom telemetry event.
@@ -6770,7 +6214,7 @@ class TelemetryProvider extends BaseModuleProvider {
6770
6214
  * provider.trackCustom({ name: 'custom_event', metadata: { foo: 'bar' } });
6771
6215
  */
6772
6216
  trackCustom(data) {
6773
- this.#state.track(TelemetryCustomEventSchema.parse({ ...data, type: TelemetryType.Custom }));
6217
+ this._track(TelemetryCustomEventSchema.parse({ ...data, type: TelemetryType.Custom }));
6774
6218
  }
6775
6219
  /**
6776
6220
  * Retrieves a telemetry adapter by its identifier.
@@ -6780,7 +6224,7 @@ class TelemetryProvider extends BaseModuleProvider {
6780
6224
  * @returns The telemetry adapter corresponding to the given identifier, or `undefined` if not found.
6781
6225
  */
6782
6226
  getAdapter(identifier) {
6783
- const adapter = this.#state.adapters[identifier];
6227
+ const adapter = this.#adapters[identifier];
6784
6228
  // Report a missing adapter as a telemetry exception rather than throwing
6785
6229
  if (!adapter) {
6786
6230
  this.trackException({
@@ -6799,17 +6243,10 @@ class TelemetryProvider extends BaseModuleProvider {
6799
6243
  * @returns True if the adapter exists, false otherwise.
6800
6244
  */
6801
6245
  hasAdapter(identifier) {
6802
- return identifier in this.#state.adapters;
6246
+ return identifier in this.#adapters;
6803
6247
  }
6804
6248
  }
6805
6249
 
6806
- /**
6807
- * Creates a reference-independent builder for either preparation or normal configuration.
6808
- * @returns A fresh telemetry configuration builder.
6809
- */
6810
- function createTelemetryConfigurator() {
6811
- return new TelemetryConfigurator();
6812
- }
6813
6250
  /**
6814
6251
  * Telemetry module definition for the Fusion Framework.
6815
6252
  *
@@ -6827,9 +6264,7 @@ function createTelemetryConfigurator() {
6827
6264
  */
6828
6265
  const module$2 = {
6829
6266
  name: 'telemetry',
6830
- disposePhase: 'diagnostics',
6831
- prepare: createTelemetryConfigurator,
6832
- configure: createTelemetryConfigurator,
6267
+ configure: () => new TelemetryConfigurator(),
6833
6268
  initialize: async (args) => {
6834
6269
  const config = await args.config.createConfigAsync(args);
6835
6270
  const event = args.hasModule('event') ? await args.requireInstance('event') : undefined;
@@ -6852,399 +6287,69 @@ const module$2 = {
6852
6287
  };
6853
6288
 
6854
6289
  /**
6855
- * Allowlist of terminal lifecycle events. Every other event, including dependency waits and
6856
- * configuration chatter, is dropped rather than projected.
6857
- */
6858
- const terminalBoundaries = new Map([
6859
- [
6860
- ModuleConfiguratorEventName.ModuleInitialized,
6861
- { operation: 'initialize', perModule: true, failure: false },
6862
- ],
6863
- [
6864
- ModuleConfiguratorEventName.ModuleInitializeError,
6865
- { operation: 'initialize', perModule: true, failure: true },
6866
- ],
6867
- [
6868
- ModuleConfiguratorEventName.ModuleDisposed,
6869
- { operation: 'dispose', perModule: true, failure: false },
6870
- ],
6871
- [
6872
- ModuleConfiguratorEventName.ModuleDisposeError,
6873
- { operation: 'dispose', perModule: true, failure: true },
6874
- ],
6875
- [
6876
- ModuleConfiguratorEventName.HostInitialized,
6877
- { operation: 'initialize', perModule: false, failure: false },
6878
- ],
6879
- [
6880
- ModuleConfiguratorEventName.HostInitializeError,
6881
- { operation: 'initialize', perModule: false, failure: true },
6882
- ],
6883
- [
6884
- ModuleConfiguratorEventName.ModulesDisposed,
6885
- { operation: 'dispose', perModule: false, failure: false },
6886
- ],
6887
- ]);
6888
- /**
6889
- * Projects only terminal lifecycle events into allowlisted operational records.
6890
- * Raw messages, arbitrary properties, errors, and dependency-wait chatter are never forwarded.
6891
- * @param configurator - Lifecycle stream to observe, preferably before user configuration runs.
6892
- * @param source - Host identity, or a lookup into the owning configuration before each record is captured.
6893
- * @returns Terminal duration metrics and controlled failure exceptions.
6894
- */
6895
- const mapConfiguratorEvents = (configurator, source = { hostId: crypto.randomUUID() }) => {
6896
- // Project before buffering so raw lifecycle payloads never enter the diagnostic queue.
6897
- return configurator.event$.pipe(mergeMap((event) => {
6898
- // Hosts prefix event names with their class name; the boundary is the unprefixed name.
6899
- const boundary = terminalBoundaries.get(event.name.split('::').at(-1) ?? '');
6900
- // Only measured terminal boundaries belong to foundational operational telemetry.
6901
- if (!boundary) {
6902
- return [];
6903
- }
6904
- // Zero is a valid measurement; missing or invalid timings cannot form a duration record.
6905
- if (event.metric === undefined || !Number.isFinite(event.metric) || event.metric < 0) {
6906
- return [];
6907
- }
6908
- let module = 'host';
6909
- // Module boundaries are attributed to a static, kebab-case module label.
6910
- if (boundary.perModule) {
6911
- const moduleName = event.properties?.moduleName;
6912
- // Aggregate legacy errors cannot be attributed to an individual module.
6913
- if (typeof moduleName !== 'string') {
6914
- return [];
6915
- }
6916
- module = moduleName.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
6917
- // Reject dynamic labels rather than admitting paths or payloads into record names.
6918
- if (!operationalIdentifier.safeParse(module).success) {
6919
- console.warn('Unsupported module identifier in lifecycle telemetry');
6920
- return [];
6921
- }
6922
- }
6923
- return createOperationalRecords({
6924
- source: typeof source === 'function' ? source() : source,
6925
- module,
6926
- operation: boundary.operation,
6927
- // Error boundaries fail by kind; host disposal isolates module failures and reports `failed`.
6928
- outcome: boundary.failure || event.properties?.failed ? 'failure' : 'success',
6929
- duration: event.metric,
6930
- });
6931
- }));
6932
- };
6933
-
6934
- /**
6935
- * Startup records retained before a provider exists. One further slot is reserved for the
6936
- * overflow summary, so a startup burst never retains more than 256 records.
6937
- */
6938
- const STARTUP_RECORD_CAPACITY = 255;
6939
- /**
6940
- * Failure evidence: an error-level record, or a measurement whose outcome failed even though
6941
- * the measurement itself is reported at a lower level.
6942
- * @param record - Buffered startup record.
6943
- * @returns Whether the record documents a failure.
6944
- */
6945
- const isFailure = (record) => record.level >= TelemetryLevel.Error || record.diagnostic?.outcome === 'failure';
6946
- /**
6947
- * Bounded buffer for records captured before a telemetry provider exists.
6290
+ * Transforms events emitted by a given modules configurator into standardized telemetry items.
6948
6291
  *
6949
- * At capacity, a failure record replaces the oldest record that is not failure evidence, so
6950
- * failures survive a startup burst; everything else is dropped and counted for an overflow
6951
- * summary.
6292
+ * @param configurator - The modules configurator instance emitting events to be mapped.
6293
+ * @returns An observable stream of `TelemetryItem` objects, each representing a mapped event.
6952
6294
  *
6953
- * @example
6954
- * ```ts
6955
- * const buffer = new StartupBuffer();
6956
- * buffer.push(record);
6957
- * const { items, dropped } = buffer.drain();
6958
- * ```
6959
- */
6960
- class StartupBuffer {
6961
- capacity;
6962
- #items = [];
6963
- #dropped = 0;
6964
- /**
6965
- * Creates an empty buffer.
6966
- * @param capacity - Records retained before records are dropped.
6967
- */
6968
- constructor(capacity = STARTUP_RECORD_CAPACITY) {
6969
- this.capacity = capacity;
6970
- }
6971
- /**
6972
- * Retains a record, preferring failure records once the buffer is full.
6973
- * @param item - Sanitized startup record.
6974
- */
6975
- push(item) {
6976
- // Retain records until capacity.
6977
- if (this.#items.length < this.capacity) {
6978
- this.#items.push(item);
6979
- return;
6980
- }
6981
- // Prefer failure evidence over successful startup measurements when capacity is exhausted.
6982
- const replace = this.#items.findIndex((record) => !isFailure(record));
6983
- // Replacing only non-failure entries prevents later successes from evicting failures.
6984
- if (isFailure(item) && replace >= 0) {
6985
- this.#items.splice(replace, 1, item);
6986
- }
6987
- this.#dropped++;
6988
- }
6989
- /**
6990
- * Takes every retained record and the dropped count, leaving the buffer empty.
6991
- * @returns Retained records, oldest first, and how many were dropped.
6992
- */
6993
- drain() {
6994
- const drained = { items: this.#items, dropped: this.#dropped };
6995
- this.#items = [];
6996
- this.#dropped = 0;
6997
- return drained;
6998
- }
6999
- }
7000
-
7001
- /**
7002
- * Captures safe host lifecycle outcomes from before any telemetry provider exists.
7003
- *
7004
- * Telemetry is itself a module, so it cannot observe the startup it is part of. A host creates
7005
- * a capture as soon as it is configured, before any user code runs, and attaches its
7006
- * {@link items} to the telemetry configurator with `attachLifecycleItems`. The capture:
7007
- * - buffers sanitized terminal records (bounded, failures preferred) until the provider
7008
- * consumes {@link items}, then forwards live;
7009
- * - when startup fails before any provider exists, hands the buffer to the host's parent,
7010
- * creating the host failure record when the host never emitted one (for example, a failing
7011
- * host configuration callback). The child's exporters and relay filter are not known yet,
7012
- * so recovery relays without ownership; nothing is dropped.
6295
+ * The function listens to the `event$` observable of the configurator and maps each event to a `TelemetryItem`.
6296
+ * - If the event contains an `error`, the item is marked as an exception.
6297
+ * - If the event contains a `metric`, the item is marked as a metric with its value.
6298
+ * - Otherwise, the item is treated as a standard event.
7013
6299
  *
7014
- * @example
7015
- * ```ts
7016
- * const source = { source: 'portal', hostId: crypto.randomUUID() };
7017
- * const capture = new StartupCapture(configurator, { source, parent });
7018
- * configurator.addConfig({
7019
- * module: telemetryModule,
7020
- * prepare: (builder) => builder.setSource(source).setParent(parent).attachLifecycleItems(capture.items),
7021
- * });
7022
- * try {
7023
- * await configureHost(configurator);
7024
- * } catch (error) {
7025
- * capture.fail();
7026
- * throw error;
7027
- * }
7028
- * ```
6300
+ * The resulting telemetry items include event name, type, level, message, properties, and a fixed scope of `['configuration']`.
7029
6301
  */
7030
- class StartupCapture {
7031
- /** Startup records followed by live outcomes, for the provider that consumes the capture. */
7032
- items;
7033
- #options;
7034
- #buffer = new StartupBuffer();
7035
- #live = new Subject();
7036
- /** Buffering until a provider consumes, then live; closed once nothing more is captured. */
7037
- #status = 'capturing';
7038
- /** The host reported its own initialize outcome, so a failure must not create another. */
7039
- #hostReported = false;
7040
- #subscription;
7041
- #startedAt = performance.now();
7042
- /**
7043
- * Starts capture immediately, not when the telemetry configuration eventually resolves.
7044
- * @param host - Host lifecycle stream.
7045
- * @param options - Host identity and the parent receiving records if startup fails.
7046
- */
7047
- constructor(host, options = {}) {
7048
- this.#options = { ...options, source: options.source ?? { hostId: crypto.randomUUID() } };
7049
- this.items = new Observable((subscriber) => {
7050
- const closed = this.#status === 'closed';
7051
- // Join live delivery first, so nothing captured while the buffer drains is lost. A closed
7052
- // capture's live subject is complete and would end the subscriber before the drain.
7053
- const live = closed ? undefined : this.#live.subscribe(subscriber);
7054
- // A capture that already closed stays closed; an active one now forwards live.
7055
- if (!closed) {
7056
- this.#status = 'live';
7057
- }
7058
- const { items, dropped } = this.#buffer.drain();
7059
- // Release the bounded startup buffer exactly once, to the provider that consumes it.
7060
- for (const item of items) {
7061
- subscriber.next(item);
7062
- }
7063
- // Overflow is reported once, alongside the records that survived it.
7064
- if (dropped) {
7065
- subscriber.next(this.#overflowSummary(dropped));
7066
- }
7067
- // A late consumer of a closed capture receives the buffer and nothing more.
7068
- if (closed) {
7069
- subscriber.complete();
7070
- }
7071
- return () => {
7072
- live?.unsubscribe();
7073
- this.#close();
7074
- };
7075
- });
7076
- this.#subscription = mapConfiguratorEvents(host, this.#options.source).subscribe({
7077
- next: (item) => {
7078
- this.#capture(item);
7079
- // Recover synchronously: a host rolling back completes its stream right after this record.
7080
- if (item.name === 'fusion.host.initialize.failure') {
7081
- this.fail();
7082
- }
7083
- },
7084
- error: () => {
7085
- console.warn('Startup telemetry capture failed');
7086
- this.fail();
7087
- },
7088
- complete: () => {
7089
- // Keep the buffer: a provider created after completion may still drain it.
7090
- this.#status = 'closed';
7091
- this.#live.complete();
6302
+ const mapConfiguratorEvents = (
6303
+ // biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
6304
+ configurator) => {
6305
+ // Map each configurator event into a standardized telemetry item shape
6306
+ return configurator.event$.pipe(map((event) => {
6307
+ const item = {
6308
+ name: event.name,
6309
+ type: TelemetryType.Event,
6310
+ level: Number(event.level),
6311
+ properties: {
6312
+ message: event.message,
6313
+ ...event.properties,
7092
6314
  },
7093
- });
7094
- // Synchronous replay can close capture before the subscription is assigned.
7095
- if (this.#status === 'closed') {
7096
- this.#subscription.unsubscribe();
7097
- }
7098
- }
7099
- /** Reports buffered startup outcomes once if no provider ever consumed the capture. */
7100
- fail() {
7101
- // Host failure callbacks and stream failures may converge on this same capture.
7102
- if (this.#status === 'closed') {
7103
- return;
7104
- }
7105
- // Configuration callback errors occur before the module host can emit its own boundary.
7106
- if (!this.#hostReported) {
7107
- // Keep the failure duration and sanitized exception together.
7108
- for (const record of createOperationalRecords({
7109
- source: this.#options.source,
7110
- module: 'host',
7111
- operation: 'initialize',
7112
- outcome: 'failure',
7113
- duration: performance.now() - this.#startedAt,
7114
- })) {
7115
- this.#capture(record);
7116
- }
7117
- }
7118
- // An attached provider keeps its records through partial cleanup and sink flush.
7119
- if (this.#status === 'live') {
7120
- return;
7121
- }
7122
- const { items, dropped } = this.#buffer.drain();
7123
- this.#close();
7124
- this.#recover(items, dropped);
7125
- }
7126
- /** Stops capture when configuration explicitly opts out before initialization. */
7127
- stop() {
7128
- this.#buffer.drain();
7129
- this.#close();
7130
- }
7131
- /**
7132
- * Records one sanitized record: buffered until a provider consumes, then forwarded live.
7133
- * @param item - Sanitized lifecycle record.
7134
- */
7135
- #capture(item) {
7136
- // Avoid manufacturing a second host outcome once the host reported its own.
7137
- if (item.diagnostic?.module === 'host' && item.diagnostic.operation === 'initialize') {
7138
- this.#hostReported = true;
7139
- }
7140
- // Live delivery avoids retaining records after the provider is ready.
7141
- if (this.#status === 'live') {
7142
- this.#live.next(item);
7143
- }
7144
- else if (this.#status === 'capturing') {
7145
- this.#buffer.push(item);
7146
- }
7147
- }
7148
- /** Ends capture: releases the host subscription and completes live delivery. */
7149
- #close() {
7150
- this.#status = 'closed';
7151
- this.#subscription?.unsubscribe();
7152
- this.#live.complete();
7153
- }
7154
- /**
7155
- * Hands buffered startup records to the parent after startup failed before a provider existed.
7156
- * @param items - Records retained before recovery.
7157
- * @param dropped - Records discarded at capacity.
7158
- */
7159
- #recover(items, dropped) {
7160
- const { parent } = this.#options;
7161
- let fallbackFailureReported = false;
7162
- // Route only the already sanitized, bounded buffer.
7163
- for (const item of items) {
7164
- // Delivered or intentionally filtered records need no local fallback.
7165
- if (this.#relayToParent(item)) {
7166
- // Move on to the next buffered record.
7167
- continue;
7168
- }
7169
- // A parent that cannot honour ownership is reported once, not once per record.
7170
- if (parent && !fallbackFailureReported) {
7171
- fallbackFailureReported = true;
7172
- console.warn('Startup telemetry fallback delivery failed or could not preserve ownership');
7173
- }
7174
- else if (!parent && item.name === 'fusion.host.initialize.failure') {
7175
- console.warn('Host initialization failed before telemetry became available');
7176
- }
7177
- }
7178
- // Make overflow visible without retaining or forwarding dropped payloads.
7179
- if (dropped) {
7180
- const summary = this.#overflowSummary(dropped);
7181
- // Local, sanitized health reporting is the last resort when no parent can take it.
7182
- if (!this.#relayToParent(summary)) {
7183
- console.warn('Startup telemetry queue overflow', summary.properties);
7184
- }
7185
- }
7186
- }
7187
- /**
7188
- * Delivers one startup record to the host's parent.
7189
- * @param item - Sanitized startup record.
7190
- * @returns Whether the record was delivered or intentionally filtered.
7191
- */
7192
- #relayToParent(item) {
7193
- const { parent } = this.#options;
7194
- // Without a parent there is nowhere to recover to.
7195
- if (!parent) {
7196
- return false;
7197
- }
7198
- try {
7199
- // The child's exporters are not configured yet, so it cannot claim ownership.
7200
- return relayRecord(parent, item, { ownsExport: false });
6315
+ scope: ['configuration'],
6316
+ };
6317
+ // An event carrying an error is reported as a telemetry exception
6318
+ if (event.error) {
6319
+ return {
6320
+ ...item,
6321
+ type: TelemetryType.Exception,
6322
+ exception: event.error,
6323
+ };
7201
6324
  }
7202
- catch {
7203
- // Parent delivery failures fall through to local health reporting.
7204
- return false;
6325
+ // An event carrying a metric value is reported as a telemetry metric
6326
+ if (event.metric) {
6327
+ return {
6328
+ ...item,
6329
+ type: TelemetryType.Metric,
6330
+ value: event.metric,
6331
+ };
7205
6332
  }
7206
- }
7207
- /**
7208
- * Creates the summary standing in for records dropped at startup capacity.
7209
- * @param dropped - Number of records discarded.
7210
- * @returns A safe overflow event carrying only the dropped count.
7211
- */
7212
- #overflowSummary(dropped) {
7213
- const { source } = this.#options;
7214
- return {
7215
- name: 'fusion.telemetry.startup-overflow',
7216
- type: TelemetryType.Event,
7217
- level: TelemetryLevel.Warning,
7218
- properties: { dropped },
7219
- // Scope the summary like the records it stands in for.
7220
- scope: operationalScope(source),
7221
- diagnostic: createDiagnosticContext({
7222
- source,
7223
- module: 'telemetry',
7224
- operation: 'startup-overflow',
7225
- outcome: 'failure',
7226
- }),
7227
- };
7228
- }
7229
- }
6333
+ return item;
6334
+ }));
6335
+ };
7230
6336
 
7231
6337
  /**
7232
6338
  * Enables telemetry for a given module configurator.
7233
6339
  *
7234
- * Registers the telemetry module and an optional configuration callback. Framework, app and
7235
- * widget hosts already capture their own startup lifecycle; `attachConfiguratorEvents` only
7236
- * matters for custom hosts and for opting out.
7237
- *
7238
6340
  * @param configurator - The module configurator instance to which telemetry should be attached.
7239
6341
  * @param options - Optional configuration for telemetry setup.
7240
- * @param options.attachConfiguratorEvents - `true` captures this configurator's lifecycle unless
7241
- * its host already does; `false` stops lifecycle records reaching this provider. A host still
7242
- * reports a failed startup to its parent.
6342
+ * @param options.attachConfiguratorEvents - If true, attaches configurator events to the telemetry builder.
7243
6343
  * @param options.configure - An optional callback to further configure the telemetry builder. Can be synchronous or asynchronous.
7244
6344
  *
7245
6345
  * @remarks
7246
- * The lifecycle option applies synchronously when this is called, so capture starts before any
7247
- * later configuration code runs. The `configure` callback is deferred until module initialization.
6346
+ * This function adds telemetry configuration to the provided configurator. It supports attaching configurator events
6347
+ * and allows for additional custom configuration via the `configure` callback.
6348
+ *
6349
+ * @remarks
6350
+ * `attachConfiguratorEvents` allows for the automatic attachment of configurator events to the telemetry builder,
6351
+ * ensuring that relevant events are captured and processed. __NOTE__ if the configurator crashes before the telemetry
6352
+ * builder is fully initialized, some events may be missed.
7248
6353
  *
7249
6354
  * @example
7250
6355
  * ```typescript
@@ -7264,19 +6369,18 @@ class StartupCapture {
7264
6369
  const enableTelemetry = (
7265
6370
  // biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
7266
6371
  configurator, options) => {
7267
- const attach = options?.attachConfiguratorEvents;
7268
6372
  configurator.addConfig({
7269
6373
  module: module$2,
7270
- // Lifecycle capture must start now, before later configuration code can run.
7271
- prepare: (builder) => {
7272
- // Opting out keeps the host's capture attached, so a later `true` restores it.
7273
- builder.setLifecycleItemsEnabled(attach);
7274
- // Hosts own their startup capture; only capture here when no host already does.
7275
- if (!builder.hasLifecycleItems()) {
7276
- builder.attachLifecycleItems(new StartupCapture(configurator).items);
7277
- }
7278
- },
7279
- configure: options?.configure,
6374
+ configure: async (builder, ref) => {
6375
+ // Attach configurator events to the telemetry builder only when opted in
6376
+ {
6377
+ builder.attachItems(mapConfiguratorEvents(configurator));
6378
+ }
6379
+ // Run the caller-supplied configure callback when one was provided
6380
+ if (options?.configure) {
6381
+ await Promise.resolve(options.configure(builder, ref));
6382
+ }
6383
+ },
7280
6384
  });
7281
6385
  };
7282
6386
 
@@ -9610,9 +8714,6 @@ class ServiceDiscoveryClient {
9610
8714
  #query;
9611
8715
  #failure;
9612
8716
  #lastKnownGood;
9613
- #diagnostic = new Subject();
9614
- /** {@inheritDoc IServiceDiscoveryClient.diagnostic$} */
9615
- diagnostic$ = this.#diagnostic.asObservable();
9616
8717
  /** Sub-path appended to the HTTP client's base URI when fetching services. */
9617
8718
  endpoint;
9618
8719
  /** HTTP client used to call the service discovery API. */
@@ -9639,40 +8740,32 @@ class ServiceDiscoveryClient {
9639
8740
  }
9640
8741
  /** {@inheritDoc IServiceDiscoveryClient.resolveServices} */
9641
8742
  async resolveServices(allow_cache) {
9642
- const started = performance.now();
9643
8743
  // Fail fast while cooling down so retry/reload/render loops never reach the network
9644
8744
  if (this.#failure && Date.now() < this.#failure.retryAt) {
9645
8745
  return this.#resolveFallback(new ServiceDiscoveryUnavailableError({
9646
8746
  cause: this.#failure.error,
9647
8747
  retryAt: this.#failure.retryAt,
9648
8748
  attempt: this.#failure.attempt,
9649
- }), started, 'cooldown');
8749
+ }));
9650
8750
  }
9651
8751
  const startedAt = Date.now();
9652
- let record;
9653
8752
  try {
9654
8753
  // firstValueFrom returns the cached snapshot; lastValueFrom waits for
9655
8754
  // the freshest value from the query
9656
8755
  const fn = allow_cache ? firstValueFrom : lastValueFrom;
9657
- record = await fn(this.#query.query());
8756
+ const record = await fn(this.#query.query());
8757
+ // Only a fresh response proves discovery works; a cached (possibly expired) snapshot must
8758
+ // not reset the failure streak or renew the age of the fallback list
8759
+ if (record.status === 'complete') {
8760
+ this.#failure = undefined;
8761
+ this.#lastKnownGood = { services: record.value, receivedAt: Date.now() };
8762
+ }
8763
+ return record.value;
9658
8764
  }
9659
8765
  catch (error) {
9660
8766
  this.#registerFailure(error, startedAt);
9661
- return this.#resolveFallback(error, started, 'network');
9662
- }
9663
- // Only a fresh response proves discovery works; a cached (possibly expired) snapshot must
9664
- // not reset the failure streak or renew the age of the fallback list
9665
- if (record.status === 'complete') {
9666
- this.#failure = undefined;
9667
- this.#lastKnownGood = { services: record.value, receivedAt: Date.now() };
9668
- }
9669
- this.#emit({
9670
- type: 'resolve',
9671
- outcome: 'success',
9672
- origin: record.status === 'complete' ? 'network' : 'cache',
9673
- duration: performance.now() - started,
9674
- });
9675
- return record.value;
8767
+ return this.#resolveFallback(error);
8768
+ }
9676
8769
  }
9677
8770
  /**
9678
8771
  * Records a failed request and schedules the next allowed request time.
@@ -9705,43 +8798,17 @@ class ServiceDiscoveryClient {
9705
8798
  * otherwise rethrows the given error.
9706
8799
  *
9707
8800
  * @param error - Error to throw when no usable fallback exists.
9708
- * @param started - `performance.now()` timestamp when resolution started.
9709
- * @param failedOrigin - Classification reported when no fallback can be served.
9710
8801
  * @returns The last known good service list.
9711
8802
  * @throws The provided `error` when no service list younger than 24 hours exists.
9712
8803
  */
9713
- #resolveFallback(error, started, failedOrigin) {
8804
+ #resolveFallback(error) {
9714
8805
  const lastKnownGood = this.#lastKnownGood;
9715
8806
  // Prefer a recent service list over an error so callers have nothing to retry
9716
8807
  if (lastKnownGood && Date.now() - lastKnownGood.receivedAt <= staleIfErrorMs) {
9717
- this.#emit({
9718
- type: 'resolve',
9719
- outcome: 'success',
9720
- origin: 'fallback',
9721
- duration: performance.now() - started,
9722
- });
9723
8808
  return lastKnownGood.services;
9724
8809
  }
9725
- this.#emit({
9726
- type: 'resolve',
9727
- outcome: 'failure',
9728
- origin: failedOrigin,
9729
- duration: performance.now() - started,
9730
- });
9731
8810
  throw error;
9732
8811
  }
9733
- /**
9734
- * Publishes a safe diagnostic outcome without letting an observer affect resolution.
9735
- * @param diagnostic - Classified outcome without keys, URIs, scopes or errors.
9736
- */
9737
- #emit(diagnostic) {
9738
- try {
9739
- this.#diagnostic.next(diagnostic);
9740
- }
9741
- catch {
9742
- // Observers are diagnostic only; resolution results must stay unchanged.
9743
- }
9744
- }
9745
8812
  /** {@inheritDoc IServiceDiscoveryClient.resolveService} */
9746
8813
  async resolveService(key, allow_cache) {
9747
8814
  const services = await this.resolveServices(allow_cache);
@@ -9749,7 +8816,6 @@ class ServiceDiscoveryClient {
9749
8816
  const service = services.find((s) => s.key === key);
9750
8817
  // Fail loudly when the requested key doesn't resolve to a known service
9751
8818
  if (!service) {
9752
- this.#emit({ type: 'unknown-service' });
9753
8819
  throw Error(`Failed to resolve service, invalid key [${key}]`);
9754
8820
  }
9755
8821
  return service;
@@ -9943,7 +9009,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
9943
9009
  }
9944
9010
 
9945
9011
  // Generated by genversion.
9946
- const version$1 = '10.3.0-next.1';
9012
+ const version$1 = '10.2.0';
9947
9013
 
9948
9014
  /**
9949
9015
  * Default implementation of {@link IServiceDiscoveryProvider}.
@@ -9966,67 +9032,6 @@ class ServiceDiscoveryProvider extends BaseModuleProvider {
9966
9032
  });
9967
9033
  this.config = config;
9968
9034
  this._http = _http;
9969
- this._addTeardown(() => {
9970
- this.#diagnostics?.unsubscribe();
9971
- this.#telemetry.disconnect();
9972
- });
9973
- }
9974
- #telemetry = createOperationalTelemetry('service-discovery');
9975
- #diagnostics;
9976
- /**
9977
- * Connects optional operational telemetry for the client this provider owns.
9978
- *
9979
- * Records one `fusion.service-discovery.resolve.duration` per network, fallback or cooldown
9980
- * resolution, plus `fallback`, `cooldown` and `unknown-service` notifications. Cached
9981
- * resolutions record only a Debug-level `cache-hit` notification, so hosts filtering at
9982
- * Information or above export nothing for them. No keys,
9983
- * URIs, scopes or errors are recorded, and HTTP attempts are left to HTTP telemetry.
9984
- * Calling it again replaces the provider; `undefined` records nothing until reconnected.
9985
- *
9986
- * @param telemetry - Host telemetry provider, or `undefined` to record nothing.
9987
- */
9988
- connectTelemetry(telemetry) {
9989
- this.#telemetry.connect(telemetry);
9990
- const diagnostic$ = this.client.diagnostic$;
9991
- // One subscription serves every connection; custom clients without a stream are skipped.
9992
- if (this.#diagnostics || !this.#telemetry.connected || !diagnostic$) {
9993
- return;
9994
- }
9995
- const report = this.#telemetry.report;
9996
- this.#diagnostics = diagnostic$.subscribe((diagnostic) => {
9997
- // An unmatched key is a notification only; it has no measured resolution of its own.
9998
- if (diagnostic.type === 'unknown-service') {
9999
- report({
10000
- notification: true,
10001
- operation: 'unknown-service',
10002
- outcome: 'failure',
10003
- level: TelemetryLevel.Warning,
10004
- });
10005
- return;
10006
- }
10007
- const { outcome, origin, duration } = diagnostic;
10008
- // Cache hits dominate lookup volume and carry no diagnostic value at default levels,
10009
- // so they are a single Debug notification rather than a measured resolution.
10010
- if (origin === 'cache') {
10011
- report({
10012
- notification: true,
10013
- operation: 'cache-hit',
10014
- outcome,
10015
- level: TelemetryLevel.Debug,
10016
- });
10017
- return;
10018
- }
10019
- report({ operation: 'resolve', outcome, duration });
10020
- // Network resolutions are fully described by the measurement and the HTTP attempt.
10021
- if (origin !== 'network') {
10022
- report({
10023
- notification: true,
10024
- operation: origin,
10025
- outcome,
10026
- level: TelemetryLevel.Warning,
10027
- });
10028
- }
10029
- });
10030
9035
  }
10031
9036
  /** {@inheritDoc IServiceDiscoveryProvider.client} */
10032
9037
  get client() {
@@ -10061,11 +9066,6 @@ class ServiceDiscoveryProvider extends BaseModuleProvider {
10061
9066
  }
10062
9067
  }
10063
9068
 
10064
- /**
10065
- * Providers this module created, so host disposal tears down only its own instances and
10066
- * their telemetry, never a provider supplied or shared by another host.
10067
- */
10068
- const ownedProviders = new WeakMap();
10069
9069
  /**
10070
9070
  * Module identifier used to register and look up the Service Discovery module
10071
9071
  * within the Fusion Framework module system.
@@ -10106,20 +9106,7 @@ const module$1 = {
10106
9106
  // service discovery requires http module to be initialized
10107
9107
  const httpModule = await requireInstance('http');
10108
9108
  // return service discovery provider
10109
- const provider = new ServiceDiscoveryProvider(config, httpModule);
10110
- ownedProviders.set(provider, provider);
10111
- return provider;
10112
- },
10113
- postInitialize: async ({ instance, modules, ref }) => {
10114
- // A child reusing its parent's client leaves reporting to the parent, so each
10115
- // resolution is recorded once, by the host that owns the client.
10116
- if (instance.client !== ref?.serviceDiscovery?.client) {
10117
- instance.connectTelemetry?.(modules.telemetry);
10118
- }
10119
- },
10120
- dispose: ({ instance }) => {
10121
- ownedProviders.get(instance)?.dispose();
10122
- ownedProviders.delete(instance);
9109
+ return new ServiceDiscoveryProvider(config, httpModule);
10123
9110
  },
10124
9111
  };
10125
9112
  /**
@@ -10622,7 +9609,7 @@ async function registerServiceWorker(framework) {
10622
9609
  }
10623
9610
 
10624
9611
  // Generated by genversion.
10625
- const version = '5.2.4-next.2';
9612
+ const version = '5.2.4';
10626
9613
 
10627
9614
  // Allow dynamic import without vite
10628
9615
  const importWithoutVite = (path) => import(/* @vite-ignore */ path);
@@ -10642,7 +9629,7 @@ enableServiceDiscovery(configurator, async (builder) => {
10642
9629
  });
10643
9630
  // Avoid interactive Entra ID authentication when the SPA runs in CI or Playwright.
10644
9631
  if (isEnabledEnvValue(import.meta.env.FUSION_SPA_MSAL_MOCK)) {
10645
- const { enableMsalMock } = await import('../index-By7EVum8.js');
9632
+ const { enableMsalMock } = await import('../mock-Do8FKTMU.js');
10646
9633
  const mockServerUrl = import.meta.env.FUSION_SPA_MSAL_MOCK_SERVER_URL;
10647
9634
  enableMsalMock(configurator, (builder) => {
10648
9635
  // The SPA owns the HTTP adapter; MSAL owns applying the returned token and account.
@@ -10665,12 +9652,12 @@ else {
10665
9652
  }
10666
9653
  // Initialize tracing before app startup so network requests and logs share active trace context.
10667
9654
  if (import.meta.env.FUSION_SPA_TELEMETRY_OTLP_TRACES_ENDPOINT) {
10668
- const { enableDevServerTracing } = await import('../enable-dev-server-tracing-ac1hNKwa.js');
9655
+ const { enableDevServerTracing } = await import('../enable-dev-server-tracing-B9rBXrzF.js');
10669
9656
  enableDevServerTracing(new URL(import.meta.env.FUSION_SPA_TELEMETRY_OTLP_TRACES_ENDPOINT, window.location.origin).href);
10670
9657
  }
10671
9658
  // The dev-server environment bridge owns activation; the SPA only consumes its endpoint.
10672
9659
  const devServerOtlpAdapter = import.meta.env.FUSION_SPA_TELEMETRY_OTLP_ENDPOINT
10673
- ? new (await import('../index-CYwi54x_.js')).OtlpAdapter({
9660
+ ? new (await import('../otlp-Dq76ZTYm.js')).OtlpAdapter({
10674
9661
  url: new URL(import.meta.env.FUSION_SPA_TELEMETRY_OTLP_ENDPOINT, window.location.origin).href,
10675
9662
  fallback: true,
10676
9663
  serviceName: 'fusion.dev-server',