@equinor/fusion-framework-vite-plugin-spa 5.2.3 → 5.2.4-next.0
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.
- package/dist/{TelemetryAdapter-B6JH_RXA.js → TelemetryAdapter-C6KyX3cq.js} +3 -2
- package/dist/TelemetryAdapter-C6KyX3cq.js.map +1 -0
- package/dist/{create-legacy-browser-delegate-4GII-R4B.js → create-legacy-browser-delegate-CNfcydOG.js} +3 -926
- package/dist/create-legacy-browser-delegate-CNfcydOG.js.map +1 -0
- package/dist/{enable-dev-server-tracing-GfRoCLxu.js → enable-dev-server-tracing-ac1hNKwa.js} +3 -2
- package/dist/{enable-dev-server-tracing-GfRoCLxu.js.map → enable-dev-server-tracing-ac1hNKwa.js.map} +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/html/bootstrap.js +1834 -745
- package/dist/html/bootstrap.js.map +1 -1
- package/dist/{index-glg6-DFO.js → index-By7EVum8.js} +3 -2
- package/dist/{index-glg6-DFO.js.map → index-By7EVum8.js.map} +1 -1
- package/dist/{index-CiqJ1Og1.js → index-CYwi54x_.js} +38 -6
- package/dist/{index-CiqJ1Og1.js.map → index-CYwi54x_.js.map} +1 -1
- package/dist/{module-BR47HvRl.js → module-BnhyaZvt.js} +1111 -208
- package/dist/module-BnhyaZvt.js.map +1 -0
- package/dist/trace-api-DzxUyN-s.js +927 -0
- package/dist/trace-api-DzxUyN-s.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/html/html.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +6 -6
- package/dist/TelemetryAdapter-B6JH_RXA.js.map +0 -1
- package/dist/create-legacy-browser-delegate-4GII-R4B.js.map +0 -1
- package/dist/module-BR47HvRl.js.map +0 -1
package/dist/html/bootstrap.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
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
|
|
2
|
-
import { B as BaseTelemetryAdapter } from '../TelemetryAdapter-
|
|
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
|
+
}
|
|
3
19
|
|
|
4
20
|
var ObjectUnsubscribedError = createErrorClass(function (_super) {
|
|
5
21
|
return function ObjectUnsubscribedErrorImpl() {
|
|
@@ -518,6 +534,18 @@ function mergeAll(concurrent) {
|
|
|
518
534
|
return mergeMap(identity, concurrent);
|
|
519
535
|
}
|
|
520
536
|
|
|
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
|
+
|
|
521
549
|
var nodeEventEmitterMethods = ['addListener', 'removeListener'];
|
|
522
550
|
var eventTargetMethods = ['addEventListener', 'removeEventListener'];
|
|
523
551
|
var jqueryMethods = ['on', 'off'];
|
|
@@ -661,6 +689,12 @@ function take(count) {
|
|
|
661
689
|
});
|
|
662
690
|
}
|
|
663
691
|
|
|
692
|
+
function ignoreElements() {
|
|
693
|
+
return operate(function (source, subscriber) {
|
|
694
|
+
source.subscribe(createOperatorSubscriber(subscriber, noop));
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
664
698
|
function distinctUntilChanged(comparator, keySelector) {
|
|
665
699
|
if (keySelector === void 0) { keySelector = identity; }
|
|
666
700
|
comparator = comparator !== null && comparator !== void 0 ? comparator : defaultCompare;
|
|
@@ -695,14 +729,23 @@ function defaultErrorFactory() {
|
|
|
695
729
|
return new EmptyError();
|
|
696
730
|
}
|
|
697
731
|
|
|
698
|
-
function
|
|
732
|
+
function exhaustMap(project, resultSelector) {
|
|
699
733
|
return operate(function (source, subscriber) {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
+
}));
|
|
706
749
|
});
|
|
707
750
|
}
|
|
708
751
|
|
|
@@ -880,42 +923,6 @@ function takeWhile(predicate, inclusive) {
|
|
|
880
923
|
});
|
|
881
924
|
}
|
|
882
925
|
|
|
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
|
-
|
|
919
926
|
function withLatestFrom() {
|
|
920
927
|
var inputs = [];
|
|
921
928
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
@@ -977,13 +984,19 @@ const ModuleConfiguratorEventBaseName = 'ModuleConfigurator';
|
|
|
977
984
|
*/
|
|
978
985
|
const ModuleConfiguratorEventName = {
|
|
979
986
|
ModuleConfigAdded: `${ModuleConfiguratorEventBaseName}.module.configAdded`,
|
|
987
|
+
PreparedConfigReplaced: `${ModuleConfiguratorEventBaseName}.module.preparedConfigReplaced`,
|
|
980
988
|
OnConfiguredAdded: `${ModuleConfiguratorEventBaseName}.onConfigured.added`,
|
|
981
989
|
OnInitializedAdded: `${ModuleConfiguratorEventBaseName}.onInitialized.added`,
|
|
982
990
|
PluginAdded: `${ModuleConfiguratorEventBaseName}.plugin.added`,
|
|
983
991
|
InitializeConfigLoaded: `${ModuleConfiguratorEventBaseName}.config.loaded`,
|
|
984
992
|
InitializeInstanceInitialized: `${ModuleConfiguratorEventBaseName}.instance.initialized`,
|
|
985
993
|
Initialize: `${ModuleConfiguratorEventBaseName}.initialize.complete`,
|
|
994
|
+
HostInitializeStarted: `${ModuleConfiguratorEventBaseName}.host.initializeStarted`,
|
|
995
|
+
HostInitialized: `${ModuleConfiguratorEventBaseName}.host.initialized`,
|
|
996
|
+
HostInitializeError: `${ModuleConfiguratorEventBaseName}.host.initializeError`,
|
|
986
997
|
ConfiguratorCreated: `${ModuleConfiguratorEventBaseName}.configurator.created`,
|
|
998
|
+
ConfiguratorPrepared: `${ModuleConfiguratorEventBaseName}.configurator.prepared`,
|
|
999
|
+
ConfiguratorPrepareError: `${ModuleConfiguratorEventBaseName}.configurator.prepareError`,
|
|
987
1000
|
ConfiguratorFailed: `${ModuleConfiguratorEventBaseName}.configurator.failed`,
|
|
988
1001
|
ModulePostConfigured: `${ModuleConfiguratorEventBaseName}.module.postConfigured`,
|
|
989
1002
|
ModulePostConfigureError: `${ModuleConfiguratorEventBaseName}.module.postConfigureError`,
|
|
@@ -1048,7 +1061,10 @@ async function createModuleConfigs(ctx, ref) {
|
|
|
1048
1061
|
const config$ = from(modules).pipe(mergeMap(async (module) => {
|
|
1049
1062
|
const configStart = performance.now();
|
|
1050
1063
|
try {
|
|
1051
|
-
|
|
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);
|
|
1052
1068
|
const configLoadTime = Math.round(performance.now() - configStart);
|
|
1053
1069
|
registerEvent({
|
|
1054
1070
|
level: ModuleEventLevel.Debug,
|
|
@@ -1188,8 +1204,8 @@ config) {
|
|
|
1188
1204
|
* Runs the full configure lifecycle phase for a set of modules.
|
|
1189
1205
|
*
|
|
1190
1206
|
* Orchestrates three sub-steps in order:
|
|
1191
|
-
* 1. {@link createModuleConfigs} —
|
|
1192
|
-
* 2. Apply all user-registered config callbacks (`_configs`)
|
|
1207
|
+
* 1. {@link createModuleConfigs} — reuse prepared builders or call `configure(ref)` factories.
|
|
1208
|
+
* 2. Apply all user-registered config callbacks (`_configs`) concurrently.
|
|
1193
1209
|
* 3. {@link runPostConfigureHooks} — call `postConfigure` and `afterConfiguration` hooks.
|
|
1194
1210
|
*
|
|
1195
1211
|
* @param ctx - The configure phase context.
|
|
@@ -1209,6 +1225,81 @@ async function runConfigurePhase(ctx, ref) {
|
|
|
1209
1225
|
return config;
|
|
1210
1226
|
}
|
|
1211
1227
|
|
|
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
|
+
|
|
1212
1303
|
/**
|
|
1213
1304
|
* Creates a `requireInstance` resolver for use during module initialization.
|
|
1214
1305
|
*
|
|
@@ -1312,6 +1403,7 @@ function createRequireInstance(moduleNames, instance$, registerEvent) {
|
|
|
1312
1403
|
async function initializeModule(module, ctx) {
|
|
1313
1404
|
const { config, ref, requireInstance, hasModule, registerEvent } = ctx;
|
|
1314
1405
|
const key = module.name;
|
|
1406
|
+
const moduleInitStart = performance.now();
|
|
1315
1407
|
// Modules must expose an initialize method to participate in the initialize phase
|
|
1316
1408
|
if (!module.initialize) {
|
|
1317
1409
|
const error = new Error(`Module ${module.name} does not have initialize method`);
|
|
@@ -1325,6 +1417,7 @@ async function initializeModule(module, ctx) {
|
|
|
1325
1417
|
moduleVersion: module.version?.toString() || 'unknown',
|
|
1326
1418
|
},
|
|
1327
1419
|
error,
|
|
1420
|
+
metric: performance.now() - moduleInitStart,
|
|
1328
1421
|
});
|
|
1329
1422
|
throw error;
|
|
1330
1423
|
}
|
|
@@ -1337,13 +1430,26 @@ async function initializeModule(module, ctx) {
|
|
|
1337
1430
|
moduleVersion: module.version?.toString() || 'unknown',
|
|
1338
1431
|
},
|
|
1339
1432
|
});
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
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
|
+
}
|
|
1347
1453
|
// Warn when providers deviate from the expected base class — these modules
|
|
1348
1454
|
// may lack version tracking and standard provider interfaces.
|
|
1349
1455
|
if (!(instance instanceof BaseModuleProvider)) {
|
|
@@ -1391,7 +1497,8 @@ async function initializeModule(module, ctx) {
|
|
|
1391
1497
|
/**
|
|
1392
1498
|
* Runs the initialize lifecycle phase for all registered modules.
|
|
1393
1499
|
*
|
|
1394
|
-
* Initializes all modules
|
|
1500
|
+
* Initializes all modules concurrently and rejects on the first failure; providers that
|
|
1501
|
+
* finish afterwards are passed to `onLateProvider` for cleanup. Cross-module
|
|
1395
1502
|
* dependency ordering is handled lazily through `requireInstance`, which
|
|
1396
1503
|
* waits for a peer module's `initialize` call to complete before resolving.
|
|
1397
1504
|
*
|
|
@@ -1408,11 +1515,11 @@ async function initializeModule(module, ctx) {
|
|
|
1408
1515
|
* initialize within its timeout window.
|
|
1409
1516
|
*/
|
|
1410
1517
|
async function runInitializePhase(ctx, config, ref) {
|
|
1411
|
-
const { modules, registerEvent } = ctx;
|
|
1518
|
+
const { modules, registerEvent, instances = {}, onLateProvider } = ctx;
|
|
1412
1519
|
// Fast-path: no modules registered — return a sealed empty instance immediately
|
|
1413
1520
|
// to avoid a subscribe/lastValueFrom race on an already-completed observable.
|
|
1414
1521
|
if (modules.length === 0) {
|
|
1415
|
-
return Object.seal(
|
|
1522
|
+
return Object.seal(instances);
|
|
1416
1523
|
}
|
|
1417
1524
|
// Extract module names for dependency lookups before any module has initialized
|
|
1418
1525
|
const moduleNames = modules.map((m) => m.name);
|
|
@@ -1423,55 +1530,56 @@ async function runInitializePhase(ctx, config, ref) {
|
|
|
1423
1530
|
const requireInstance = createRequireInstance(moduleNames, instance$, registerEvent);
|
|
1424
1531
|
// Initialize all modules concurrently; modules that depend on peers will
|
|
1425
1532
|
// suspend inside requireInstance until the dependency resolves.
|
|
1426
|
-
const init$ = from(modules).pipe(mergeMap((module) => initializeModule(module, { config, ref, requireInstance, hasModule, registerEvent })));
|
|
1427
1533
|
const initStart = performance.now();
|
|
1428
|
-
|
|
1429
|
-
//
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
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,
|
|
1459
1570
|
});
|
|
1460
|
-
const instanceInitStart = performance.now();
|
|
1461
|
-
const instance = await lastValueFrom(instance$);
|
|
1462
|
-
const initTime = Math.round(performance.now() - instanceInitStart);
|
|
1463
1571
|
registerEvent({
|
|
1464
1572
|
level: ModuleEventLevel.Debug,
|
|
1465
1573
|
name: ModuleConfiguratorEventName.InitializeComplete,
|
|
1466
1574
|
message: `Modules instance created in ${initTime}ms`,
|
|
1467
1575
|
properties: {
|
|
1468
|
-
modules: Object.keys(
|
|
1576
|
+
modules: Object.keys(instances).join(', '),
|
|
1469
1577
|
initTime,
|
|
1470
1578
|
},
|
|
1471
1579
|
metric: initTime,
|
|
1472
1580
|
});
|
|
1473
|
-
Object.seal(
|
|
1474
|
-
return
|
|
1581
|
+
Object.seal(instances);
|
|
1582
|
+
return instances;
|
|
1475
1583
|
}
|
|
1476
1584
|
|
|
1477
1585
|
// biome-ignore-all lint/suspicious/noExplicitAny: internal type-erased dispatch — the post-initialize phase forwards opaque instances without inspecting their concrete shapes
|
|
@@ -1710,6 +1818,8 @@ async function runPluginPhase(ctx, modules, ref) {
|
|
|
1710
1818
|
});
|
|
1711
1819
|
}
|
|
1712
1820
|
|
|
1821
|
+
/** Fixed teardown order: ordinary modules first, so diagnostics and events can still record them. */
|
|
1822
|
+
const disposePhaseOrder = ['default', 'diagnostics', 'events'];
|
|
1713
1823
|
function getPluginTeardownName(teardown) {
|
|
1714
1824
|
return typeof teardown === 'function' ? teardown.name || 'anonymous' : 'dispose';
|
|
1715
1825
|
}
|
|
@@ -1724,9 +1834,9 @@ async function runPluginTeardown(teardown) {
|
|
|
1724
1834
|
/**
|
|
1725
1835
|
* Runs the dispose lifecycle phase for all registered modules.
|
|
1726
1836
|
*
|
|
1727
|
-
* Calls
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
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.
|
|
1730
1840
|
*
|
|
1731
1841
|
* @param ctx - The dispose phase context.
|
|
1732
1842
|
* @param instance - The initialized module instance map to tear down.
|
|
@@ -1736,6 +1846,8 @@ async function runPluginTeardown(teardown) {
|
|
|
1736
1846
|
*/
|
|
1737
1847
|
async function runDisposePhase(ctx, instance, ref) {
|
|
1738
1848
|
const { modules, registerEvent, event$, pluginTeardowns = [] } = ctx;
|
|
1849
|
+
const start = performance.now();
|
|
1850
|
+
let failed = false;
|
|
1739
1851
|
registerEvent({
|
|
1740
1852
|
level: ModuleEventLevel.Debug,
|
|
1741
1853
|
name: ModuleConfiguratorEventName.Dispose,
|
|
@@ -1764,6 +1876,7 @@ async function runDisposePhase(ctx, instance, ref) {
|
|
|
1764
1876
|
});
|
|
1765
1877
|
}
|
|
1766
1878
|
catch (err) {
|
|
1879
|
+
failed = true;
|
|
1767
1880
|
registerEvent({
|
|
1768
1881
|
level: ModuleEventLevel.Warning,
|
|
1769
1882
|
name: ModuleConfiguratorEventName.PluginDisposeError,
|
|
@@ -1780,65 +1893,98 @@ async function runDisposePhase(ctx, instance, ref) {
|
|
|
1780
1893
|
message: 'Disposing modules',
|
|
1781
1894
|
properties: { count: modules.length },
|
|
1782
1895
|
});
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
await module.dispose({
|
|
1795
|
-
ref,
|
|
1796
|
-
modules: instance,
|
|
1797
|
-
instance: instance[module.name],
|
|
1798
|
-
});
|
|
1799
|
-
registerEvent({
|
|
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
|
-
},
|
|
1807
|
-
});
|
|
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);
|
|
1808
1907
|
}
|
|
1809
|
-
|
|
1908
|
+
else {
|
|
1909
|
+
failed = true;
|
|
1810
1910
|
registerEvent({
|
|
1811
1911
|
level: ModuleEventLevel.Warning,
|
|
1812
|
-
name: ModuleConfiguratorEventName.
|
|
1813
|
-
message: `
|
|
1814
|
-
properties: {
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
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'),
|
|
1916
|
+
});
|
|
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') {
|
|
1965
|
+
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 },
|
|
1819
1971
|
});
|
|
1820
1972
|
}
|
|
1821
|
-
}
|
|
1822
|
-
registerEvent({
|
|
1823
|
-
level: ModuleEventLevel.Debug,
|
|
1824
|
-
name: ModuleConfiguratorEventName.ModulesDisposed,
|
|
1825
|
-
message: 'Module dispose complete',
|
|
1826
|
-
properties: { count: modules.length },
|
|
1827
|
-
});
|
|
1973
|
+
}
|
|
1828
1974
|
// Complete the event stream last so all dispose events are captured before
|
|
1829
1975
|
// any subscriber teardown triggered by completion.
|
|
1830
1976
|
event$.complete();
|
|
1831
1977
|
}
|
|
1832
1978
|
|
|
1833
1979
|
// Generated by genversion.
|
|
1834
|
-
const version$4 = '6.
|
|
1980
|
+
const version$4 = '6.2.0-next.0';
|
|
1835
1981
|
|
|
1836
1982
|
// 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
|
|
1837
1983
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1838
1984
|
/**
|
|
1839
1985
|
* Core orchestrator that drives the module lifecycle in Fusion Framework.
|
|
1840
1986
|
*
|
|
1841
|
-
* `ModulesConfigurator` manages the full **configure → post-configure → initialize →
|
|
1987
|
+
* `ModulesConfigurator` manages the full **prepare → configure → post-configure → initialize →
|
|
1842
1988
|
* post-initialize → plugin → dispose** pipeline for a set of modules. Consumers
|
|
1843
1989
|
* register modules via {@link addConfig} or {@link configure}, then call
|
|
1844
1990
|
* {@link initialize} to produce a sealed {@link ModulesInstance} whose properties
|
|
@@ -1848,6 +1994,7 @@ const version$4 = '6.1.6';
|
|
|
1848
1994
|
*
|
|
1849
1995
|
* | # | Phase | Entry point | Description |
|
|
1850
1996
|
* |---|-------|-------------|-------------|
|
|
1997
|
+
* | 0 | **Prepare** | `addConfig` | Opt-in synchronous preparation runs during registration, before startup callbacks can fail. |
|
|
1851
1998
|
* | 1 | **Configure** | `_configure` | Each module's `configure()` factory creates a config builder; registered callbacks mutate it. |
|
|
1852
1999
|
* | 2 | **Post-configure** | `_postConfigure` (inside `_configure`) | `postConfigure()` hooks and `onConfigured` callbacks run. |
|
|
1853
2000
|
* | 3 | **Initialize** | `_initialize` | Modules are initialized concurrently; cross-module dependencies are resolved through `requireInstance`. |
|
|
@@ -1899,6 +2046,10 @@ class ModulesConfigurator {
|
|
|
1899
2046
|
// configuration before telemetry is wired up.
|
|
1900
2047
|
// Memory bound: ~24 KB at ~240 bytes/event × 100 events.
|
|
1901
2048
|
#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();
|
|
1902
2053
|
/**
|
|
1903
2054
|
* Stream of lifecycle events emitted while modules are configured, initialized,
|
|
1904
2055
|
* and disposed.
|
|
@@ -1949,10 +2100,14 @@ class ModulesConfigurator {
|
|
|
1949
2100
|
* Teardown callbacks returned by registered plugins.
|
|
1950
2101
|
*
|
|
1951
2102
|
* Consumed during dispose and cleared after execution so repeated dispose calls
|
|
1952
|
-
* do not run plugin cleanup more than once.
|
|
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.
|
|
1953
2106
|
* @protected
|
|
1954
2107
|
*/
|
|
1955
2108
|
_pluginTeardowns = [];
|
|
2109
|
+
/** Plugin teardowns owned by each initialized instance, so disposing one never tears down another. */
|
|
2110
|
+
#instanceTeardowns = new WeakMap();
|
|
1956
2111
|
/**
|
|
1957
2112
|
* Set of all registered module descriptors.
|
|
1958
2113
|
* Uses a `Set` for automatic deduplication — the same module registered twice
|
|
@@ -2025,14 +2180,22 @@ class ModulesConfigurator {
|
|
|
2025
2180
|
*
|
|
2026
2181
|
* Adds the module to the known module set and registers the optional
|
|
2027
2182
|
* `configure`, `afterConfig`, and `afterInit` callbacks into their
|
|
2028
|
-
* respective lifecycle phase arrays.
|
|
2183
|
+
* respective lifecycle phase arrays. An optional `prepare` callback runs
|
|
2184
|
+
* synchronously during registration against the same builder.
|
|
2029
2185
|
*
|
|
2030
2186
|
* @param config - The module configurator descriptor to register.
|
|
2031
2187
|
* @template T - The module type being registered.
|
|
2032
2188
|
* @template TConfig - The resolved configuration type for the module.
|
|
2189
|
+
* @throws Rethrows preparation failures; async preparation and unprepared ordinary factories are rejected.
|
|
2033
2190
|
*/
|
|
2034
2191
|
addConfig(config) {
|
|
2035
2192
|
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
|
+
});
|
|
2036
2199
|
// Find an existing descriptor so re-registering a name can replace all of its lifecycle hooks.
|
|
2037
2200
|
const existingModule = Array.from(this._modules).find((m) => m.name === module.name);
|
|
2038
2201
|
// Only handle replacement/dedupe when a descriptor is already registered under this name.
|
|
@@ -2043,7 +2206,19 @@ class ModulesConfigurator {
|
|
|
2043
2206
|
// all share one module singleton) must stay additive so multiple calls can register
|
|
2044
2207
|
// multiple named clients without clobbering each other.
|
|
2045
2208
|
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
|
+
}
|
|
2046
2220
|
this._removeModuleCallbacks(module.name);
|
|
2221
|
+
this.#configurators.delete(existingModule);
|
|
2047
2222
|
const modules = Array.from(this._modules)
|
|
2048
2223
|
// Preserve every descriptor while substituting the newly registered module.
|
|
2049
2224
|
.map((m) => (m.name === module.name ? module : m));
|
|
@@ -2053,6 +2228,10 @@ class ModulesConfigurator {
|
|
|
2053
2228
|
else {
|
|
2054
2229
|
this._modules.add(module);
|
|
2055
2230
|
}
|
|
2231
|
+
// Commit the prepared builder only once the registration itself is in place.
|
|
2232
|
+
if (prepared) {
|
|
2233
|
+
this.#configurators.set(module, prepared.configurator);
|
|
2234
|
+
}
|
|
2056
2235
|
this._registerEvent({
|
|
2057
2236
|
level: ModuleEventLevel.Debug,
|
|
2058
2237
|
name: ModuleConfiguratorEventName.ModuleConfigAdded,
|
|
@@ -2175,8 +2354,64 @@ class ModulesConfigurator {
|
|
|
2175
2354
|
* @returns A promise resolving to the sealed, initialized module instance.
|
|
2176
2355
|
* @template T - Additional modules to merge into the instance type.
|
|
2177
2356
|
* @template R - The reference type, narrowed to `TRef`.
|
|
2357
|
+
* @throws Rethrows the original configuration or initialization error after partial cleanup.
|
|
2178
2358
|
*/
|
|
2179
2359
|
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) {
|
|
2180
2415
|
const configStart = performance.now();
|
|
2181
2416
|
const config = await this._configure(ref);
|
|
2182
2417
|
const configLoadTime = Math.round(performance.now() - configStart);
|
|
@@ -2195,7 +2430,7 @@ class ModulesConfigurator {
|
|
|
2195
2430
|
metric: configLoadTime,
|
|
2196
2431
|
});
|
|
2197
2432
|
const instanceStart = performance.now();
|
|
2198
|
-
const instance = await this._initialize(config, ref);
|
|
2433
|
+
const instance = await this._initialize(config, ref, instances);
|
|
2199
2434
|
const instanceLoadTime = Math.round(performance.now() - instanceStart);
|
|
2200
2435
|
// Build a comma-separated module name list for telemetry properties
|
|
2201
2436
|
// Extract just the module names before joining into a display string
|
|
@@ -2230,10 +2465,13 @@ class ModulesConfigurator {
|
|
|
2230
2465
|
await this._postInitialize(instance, ref);
|
|
2231
2466
|
// `instance` is the freshly built module instance record, which is structurally compatible
|
|
2232
2467
|
// 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.
|
|
2233
2470
|
const modules = Object.seal(Object.assign({}, instance, {
|
|
2234
|
-
dispose: () => this.dispose(
|
|
2471
|
+
dispose: () => this.dispose(modules),
|
|
2235
2472
|
}));
|
|
2236
|
-
|
|
2473
|
+
this.#instanceTeardowns.set(modules, teardowns);
|
|
2474
|
+
await this._registerPlugins(modules, ref, teardowns);
|
|
2237
2475
|
return modules;
|
|
2238
2476
|
}
|
|
2239
2477
|
/**
|
|
@@ -2271,6 +2509,7 @@ class ModulesConfigurator {
|
|
|
2271
2509
|
async _configure(ref) {
|
|
2272
2510
|
return runConfigurePhase({
|
|
2273
2511
|
modules: this.modules,
|
|
2512
|
+
configurators: this.#configurators,
|
|
2274
2513
|
configs: this._configs,
|
|
2275
2514
|
afterConfiguration: this._afterConfiguration,
|
|
2276
2515
|
afterInit: this._afterInit,
|
|
@@ -2287,17 +2526,39 @@ class ModulesConfigurator {
|
|
|
2287
2526
|
*
|
|
2288
2527
|
* @param config - The merged module config map from the configure phase.
|
|
2289
2528
|
* @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.
|
|
2290
2530
|
* @returns A promise resolving to the sealed map of initialized module providers.
|
|
2291
2531
|
* @template T - Additional modules to merge into the instance type.
|
|
2292
2532
|
* @template R - The reference type. Defaults to `TRef`.
|
|
2293
2533
|
* @protected
|
|
2294
2534
|
*/
|
|
2295
|
-
async _initialize(config, ref) {
|
|
2535
|
+
async _initialize(config, ref, instances = {}) {
|
|
2296
2536
|
return runInitializePhase({
|
|
2297
2537
|
modules: this.modules,
|
|
2298
2538
|
registerEvent: this._registerEvent.bind(this),
|
|
2539
|
+
instances,
|
|
2540
|
+
onLateProvider: (module, provider) => {
|
|
2541
|
+
void ModulesConfigurator.#disposeLateProvider(module, provider, ref);
|
|
2542
|
+
},
|
|
2299
2543
|
}, config, ref);
|
|
2300
2544
|
}
|
|
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
|
+
}
|
|
2301
2562
|
/**
|
|
2302
2563
|
* Runs the post-initialize lifecycle phase.
|
|
2303
2564
|
*
|
|
@@ -2329,14 +2590,16 @@ class ModulesConfigurator {
|
|
|
2329
2590
|
*
|
|
2330
2591
|
* @param instance - The sealed module instance from the initialize phase.
|
|
2331
2592
|
* @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.
|
|
2332
2595
|
* @template T - Additional modules to merge into the instance type.
|
|
2333
2596
|
* @template R - The reference type, narrowed to `TRef`.
|
|
2334
2597
|
* @protected
|
|
2335
2598
|
*/
|
|
2336
|
-
async _registerPlugins(instance, ref) {
|
|
2599
|
+
async _registerPlugins(instance, ref, teardowns = this._pluginTeardowns) {
|
|
2337
2600
|
return runPluginPhase({
|
|
2338
2601
|
plugins: this._plugins,
|
|
2339
|
-
teardowns
|
|
2602
|
+
teardowns,
|
|
2340
2603
|
registerEvent: this._registerEvent.bind(this),
|
|
2341
2604
|
}, instance, ref);
|
|
2342
2605
|
}
|
|
@@ -2350,14 +2613,26 @@ class ModulesConfigurator {
|
|
|
2350
2613
|
* @param ref - Optional reference forwarded to module dispose hooks.
|
|
2351
2614
|
* @returns A promise resolving when all modules have been disposed.
|
|
2352
2615
|
*/
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
pluginTeardowns
|
|
2360
|
-
|
|
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;
|
|
2361
2636
|
}
|
|
2362
2637
|
}
|
|
2363
2638
|
|
|
@@ -2994,7 +3269,7 @@ class HttpClientConfigurator {
|
|
|
2994
3269
|
}
|
|
2995
3270
|
|
|
2996
3271
|
// Generated by genversion.
|
|
2997
|
-
const version$3 = '8.1.
|
|
3272
|
+
const version$3 = '8.1.5-next.0';
|
|
2998
3273
|
|
|
2999
3274
|
/** URL protocols accepted as valid ad-hoc base URIs. */
|
|
3000
3275
|
const SUPPORTED_PROTOCOLS = ['http:', 'https:', 'ws:', 'wss:'];
|
|
@@ -3720,9 +3995,10 @@ const module$3 = {
|
|
|
3720
3995
|
// only attempt to acquire a token when the request actually declares scopes
|
|
3721
3996
|
if (scopes.length) {
|
|
3722
3997
|
/** TODO(#5143): should be try catch, check caller for handling */
|
|
3723
|
-
const
|
|
3724
|
-
|
|
3725
|
-
|
|
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);
|
|
3726
4002
|
// without a token there's nothing to attach, fall through to the default request
|
|
3727
4003
|
if (accessToken) {
|
|
3728
4004
|
const headers = new Headers(request.headers);
|
|
@@ -4353,7 +4629,7 @@ function peek(draft, prop) {
|
|
|
4353
4629
|
return source[prop];
|
|
4354
4630
|
}
|
|
4355
4631
|
function isRelocatedBaseRef(state, prop, value) {
|
|
4356
|
-
if (state.type_ !== 1 /* Array */ || !state.allIndicesReassigned_ ||
|
|
4632
|
+
if (state.type_ !== 1 /* Array */ || !state.allIndicesReassigned_ || !isDraftable(value) || value[DRAFT_STATE]) {
|
|
4357
4633
|
return false;
|
|
4358
4634
|
}
|
|
4359
4635
|
return state.baseRefs_.has(value);
|
|
@@ -4599,8 +4875,11 @@ function currentImpl(value) {
|
|
|
4599
4875
|
} else {
|
|
4600
4876
|
copy = shallowCopy(value, true);
|
|
4601
4877
|
}
|
|
4878
|
+
const source = copy;
|
|
4879
|
+
if (isSet(copy))
|
|
4880
|
+
copy = /* @__PURE__ */ new Set();
|
|
4602
4881
|
each(
|
|
4603
|
-
|
|
4882
|
+
source,
|
|
4604
4883
|
(key, childValue) => {
|
|
4605
4884
|
set(copy, key, currentImpl(childValue));
|
|
4606
4885
|
},
|
|
@@ -4855,84 +5134,6 @@ class FlowSubject extends Observable {
|
|
|
4855
5134
|
}
|
|
4856
5135
|
}
|
|
4857
5136
|
|
|
4858
|
-
/**
|
|
4859
|
-
* Internal helper utilities for constructing and inspecting Redux-style
|
|
4860
|
-
* lifecycle action types (`::request`, `::success`, `::failure`).
|
|
4861
|
-
*/
|
|
4862
|
-
/**
|
|
4863
|
-
* Separates an action's base type from its lifecycle suffix.
|
|
4864
|
-
*/
|
|
4865
|
-
const actionSuffixDivider = '::';
|
|
4866
|
-
|
|
4867
|
-
// 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
|
|
4868
|
-
// 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
|
|
4869
|
-
/**
|
|
4870
|
-
* taken from https://github.com/reduxjs/redux-toolkit/tree/master/packages/toolkit/src
|
|
4871
|
-
*/
|
|
4872
|
-
/**
|
|
4873
|
-
* Creates the runtime action creator implementation shared by the overloads above.
|
|
4874
|
-
*
|
|
4875
|
-
* @param type - The action type to assign to created actions.
|
|
4876
|
-
* @param prepareAction - An optional function that prepares payload, metadata, and errors.
|
|
4877
|
-
* @returns A callable action creator with type and matching metadata.
|
|
4878
|
-
* @throws {Error} When the prepare callback does not return an action object.
|
|
4879
|
-
*/
|
|
4880
|
-
function createAction(type, prepareAction) {
|
|
4881
|
-
function actionCreator(...args) {
|
|
4882
|
-
// Use the prepare callback when the caller needs custom payload construction.
|
|
4883
|
-
if (prepareAction) {
|
|
4884
|
-
const prepared = prepareAction(...args);
|
|
4885
|
-
// Reject invalid prepare callbacks before reading their action fields.
|
|
4886
|
-
if (!prepared) {
|
|
4887
|
-
throw new Error('prepareAction did not return an object');
|
|
4888
|
-
}
|
|
4889
|
-
// Merge optional metadata and error fields into the prepared action.
|
|
4890
|
-
return {
|
|
4891
|
-
type,
|
|
4892
|
-
payload: prepared.payload,
|
|
4893
|
-
...('meta' in prepared && { meta: prepared.meta }),
|
|
4894
|
-
...('error' in prepared && { error: prepared.error }),
|
|
4895
|
-
};
|
|
4896
|
-
}
|
|
4897
|
-
return { type, payload: args[0] };
|
|
4898
|
-
}
|
|
4899
|
-
actionCreator.toString = () => `${type}`;
|
|
4900
|
-
actionCreator.type = type;
|
|
4901
|
-
actionCreator.match = (action) => action.type === type;
|
|
4902
|
-
return actionCreator;
|
|
4903
|
-
}
|
|
4904
|
-
|
|
4905
|
-
// biome-ignore-all lint/suspicious/noExplicitAny: generic constraints mirror create-action.ts's ported redux-toolkit PrepareAction<any> dispatch pattern
|
|
4906
|
-
/**
|
|
4907
|
-
* Creates the runtime async action implementation shared by the overloads above.
|
|
4908
|
-
*
|
|
4909
|
-
* @param type - The base action type string.
|
|
4910
|
-
* @param request - A prepare function for the request action payload.
|
|
4911
|
-
* @param success - A prepare function for the success action payload.
|
|
4912
|
-
* @param failure - An optional prepare function for the failure action payload.
|
|
4913
|
-
* @returns An action creator with request, success, and optional failure actions.
|
|
4914
|
-
*/
|
|
4915
|
-
function createAsyncAction(type, request, success, failure) {
|
|
4916
|
-
const action = createAction([type, 'request'].join(actionSuffixDivider), request);
|
|
4917
|
-
// Attach the success action only when the caller supplied its prepare function.
|
|
4918
|
-
if (success) {
|
|
4919
|
-
// Add the success action while preserving the request creator's identity.
|
|
4920
|
-
Object.assign(action, {
|
|
4921
|
-
success: createAction([type, 'success'].join(actionSuffixDivider), success),
|
|
4922
|
-
});
|
|
4923
|
-
}
|
|
4924
|
-
// Attach the failure action only for async workflows that support failure results.
|
|
4925
|
-
if (failure) {
|
|
4926
|
-
// Add the failure action alongside the existing request and success actions.
|
|
4927
|
-
Object.assign(action, {
|
|
4928
|
-
failure: createAction([type, 'failure'].join(actionSuffixDivider), failure),
|
|
4929
|
-
});
|
|
4930
|
-
}
|
|
4931
|
-
// `action` is progressively mutated with `success`/`failure` properties above, so its static
|
|
4932
|
-
// type can't reflect the final shape declared by this function's overload signatures.
|
|
4933
|
-
return action;
|
|
4934
|
-
}
|
|
4935
|
-
|
|
4936
5137
|
function freezeDraftable(val) {
|
|
4937
5138
|
return isDraftable(val) ? produce(val, () => { }) : val;
|
|
4938
5139
|
}
|
|
@@ -5229,12 +5430,14 @@ const mergeTelemetryItem = (target, source) => {
|
|
|
5229
5430
|
* Configures telemetry settings for the application.
|
|
5230
5431
|
*
|
|
5231
5432
|
* The `TelemetryConfigurator` class extends `BaseConfigBuilder` to provide a fluent API for
|
|
5232
|
-
* setting up telemetry adapters, metadata, default scopes,
|
|
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.
|
|
5233
5436
|
*
|
|
5234
5437
|
* @example
|
|
5235
5438
|
* ```typescript
|
|
5236
5439
|
* const configurator = new TelemetryConfigurator()
|
|
5237
|
-
* .setAdapter(
|
|
5440
|
+
* .setAdapter('console', consoleAdapter)
|
|
5238
5441
|
* .setMetadata({ app: 'my-app' })
|
|
5239
5442
|
* .setDefaultScope(['user', 'session'])
|
|
5240
5443
|
* .setParent(parentProvider);
|
|
@@ -5250,7 +5453,7 @@ const mergeTelemetryItem = (target, source) => {
|
|
|
5250
5453
|
* @see ITelemetryConfigurator
|
|
5251
5454
|
*/
|
|
5252
5455
|
class TelemetryConfigurator extends BaseConfigBuilder {
|
|
5253
|
-
#
|
|
5456
|
+
#adapters = {};
|
|
5254
5457
|
#metadata = [];
|
|
5255
5458
|
/**
|
|
5256
5459
|
* Creates a new `TelemetryConfigurator` and wires up the async adapter
|
|
@@ -5261,40 +5464,56 @@ class TelemetryConfigurator extends BaseConfigBuilder {
|
|
|
5261
5464
|
// Configure async adapter resolution using mergeMap to handle Promise/Observable adapter factories
|
|
5262
5465
|
this._set('adapters', (args) => {
|
|
5263
5466
|
// Resolve every adapter factory concurrently and merge them into a single record
|
|
5264
|
-
return from(Object.entries(this.#
|
|
5467
|
+
return from(Object.entries(this.#adapters)).pipe(mergeMap(([identifier, { configure }]) =>
|
|
5265
5468
|
// Drop adapters whose factory resolved to a falsy value
|
|
5266
|
-
from(
|
|
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]) => {
|
|
5267
5474
|
acc[identifier] = adapter;
|
|
5268
5475
|
return acc;
|
|
5269
5476
|
}, {}), defaultIfEmpty({}), shareReplay({ bufferSize: 1, refCount: true }));
|
|
5270
5477
|
});
|
|
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));
|
|
5271
5483
|
// Configure metadata merging - handles multiple sync/async metadata sources
|
|
5272
5484
|
this._set('metadata', async () => {
|
|
5273
5485
|
const metadataItems = this.#metadata;
|
|
5274
5486
|
return (...args) =>
|
|
5275
5487
|
// Merge every registered metadata source in order, emitting the final accumulated result
|
|
5276
|
-
from(metadataItems).pipe(concatMap((metadata) => toObservable(metadata, ...args)), scan((acc, current) => mergeMetadata(acc, current) ?? {}, {}), last(), shareReplay({ bufferSize: 1, refCount: true }));
|
|
5488
|
+
from(metadataItems).pipe(concatMap((metadata) => toObservable(metadata, ...args)), scan((acc, current) => mergeMetadata(acc, current) ?? {}, {}), defaultIfEmpty({}), last(), shareReplay({ bufferSize: 1, refCount: true }));
|
|
5277
5489
|
});
|
|
5278
5490
|
}
|
|
5279
5491
|
/**
|
|
5280
5492
|
* Registers a telemetry adapter with the configurator.
|
|
5281
5493
|
*
|
|
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
|
+
*
|
|
5282
5497
|
* @param identifier - The key used to register and later resolve this adapter.
|
|
5283
|
-
* @param adapter - The telemetry adapter to be added.
|
|
5498
|
+
* @param adapter - The telemetry adapter to be added.
|
|
5284
5499
|
* @returns The current instance of the configurator for method chaining.
|
|
5285
5500
|
*/
|
|
5286
5501
|
setAdapter(identifier, adapter) {
|
|
5287
|
-
|
|
5502
|
+
this.#adapters[identifier] = { configure: async () => adapter, explicit: !adapter.fallback };
|
|
5503
|
+
return this;
|
|
5288
5504
|
}
|
|
5289
5505
|
/**
|
|
5290
5506
|
* Configures a telemetry adapter with the configurator.
|
|
5291
5507
|
*
|
|
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
|
+
*
|
|
5292
5511
|
* @param identifier - The key used to register and later resolve this adapter.
|
|
5293
5512
|
* @param adapterFn - A callback function that returns a telemetry adapter instance
|
|
5294
5513
|
* @returns The current instance for method chaining
|
|
5295
5514
|
*/
|
|
5296
5515
|
configureAdapter(identifier, adapterFn) {
|
|
5297
|
-
this.#
|
|
5516
|
+
this.#adapters[identifier] = { configure: adapterFn, explicit: true };
|
|
5298
5517
|
return this;
|
|
5299
5518
|
}
|
|
5300
5519
|
/**
|
|
@@ -5330,6 +5549,15 @@ class TelemetryConfigurator extends BaseConfigBuilder {
|
|
|
5330
5549
|
this._set('parent', parent);
|
|
5331
5550
|
return this;
|
|
5332
5551
|
}
|
|
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
|
+
}
|
|
5333
5561
|
/**
|
|
5334
5562
|
* Attaches an observable stream of telemetry items to the configurator.
|
|
5335
5563
|
*
|
|
@@ -5340,6 +5568,20 @@ class TelemetryConfigurator extends BaseConfigBuilder {
|
|
|
5340
5568
|
this._set('items$', item$);
|
|
5341
5569
|
return this;
|
|
5342
5570
|
}
|
|
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
|
+
}
|
|
5343
5585
|
/**
|
|
5344
5586
|
* Sets the filter configuration for telemetry items.
|
|
5345
5587
|
*
|
|
@@ -5355,27 +5597,172 @@ class TelemetryConfigurator extends BaseConfigBuilder {
|
|
|
5355
5597
|
}
|
|
5356
5598
|
|
|
5357
5599
|
// Generated by genversion.
|
|
5358
|
-
const version$2 = '
|
|
5600
|
+
const version$2 = '8.0.0-next.0';
|
|
5359
5601
|
|
|
5360
5602
|
/**
|
|
5361
|
-
* Represents a
|
|
5362
|
-
*
|
|
5363
|
-
* The `FrameworkEvent` class implements the `IFrameworkEvent` interface and provides a way to create and manage events
|
|
5364
|
-
* with custom details and behavior. It allows you to:
|
|
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.
|
|
5365
5605
|
*
|
|
5366
|
-
*
|
|
5367
|
-
* -
|
|
5368
|
-
* -
|
|
5369
|
-
*
|
|
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.
|
|
5370
5611
|
*
|
|
5371
|
-
*
|
|
5372
|
-
*
|
|
5612
|
+
* @example
|
|
5613
|
+
* ```typescript
|
|
5614
|
+
* const measurement = new Measurement(provider, { name: 'operation' });
|
|
5615
|
+
* // ... perform operation ...
|
|
5616
|
+
* measurement.measure();
|
|
5617
|
+
* ```
|
|
5373
5618
|
*
|
|
5374
|
-
* @
|
|
5375
|
-
* @template TType The type of the event type.
|
|
5619
|
+
* @public
|
|
5376
5620
|
*/
|
|
5377
|
-
class
|
|
5378
|
-
|
|
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:
|
|
5752
|
+
*
|
|
5753
|
+
* - Specify the event type and initial event details
|
|
5754
|
+
* - Access the event details
|
|
5755
|
+
* - Check if the event is cancelable and whether it has been canceled
|
|
5756
|
+
* - Control whether the event can bubble up the event hierarchy
|
|
5757
|
+
*
|
|
5758
|
+
* The `FrameworkEvent` class is designed to be used as a base class for creating custom event types that fit the needs
|
|
5759
|
+
* of your application or framework.
|
|
5760
|
+
*
|
|
5761
|
+
* @template TInit The type of the event details.
|
|
5762
|
+
* @template TType The type of the event type.
|
|
5763
|
+
*/
|
|
5764
|
+
class FrameworkEvent {
|
|
5765
|
+
__type;
|
|
5379
5766
|
#detail;
|
|
5380
5767
|
#source;
|
|
5381
5768
|
#canceled = false;
|
|
@@ -5519,148 +5906,34 @@ class TelemetryEvent extends FrameworkEvent {
|
|
|
5519
5906
|
}
|
|
5520
5907
|
|
|
5521
5908
|
/**
|
|
5522
|
-
*
|
|
5523
|
-
* to a telemetry provider. Supports cloning, resetting, and marking measurements as completed.
|
|
5909
|
+
* Relays one record to a parent provider under the child's routing policy.
|
|
5524
5910
|
*
|
|
5525
|
-
*
|
|
5526
|
-
*
|
|
5527
|
-
* - Provides methods to measure elapsed time, reset the timer, clone the measurement, and
|
|
5528
|
-
* automatically measure on disposal if not already measured.
|
|
5529
|
-
* - Can be used to wrap asynchronous operations and automatically record their duration.
|
|
5911
|
+
* Shared by live provider relay and startup recovery, so both honour the same relay filter
|
|
5912
|
+
* and exporter ownership.
|
|
5530
5913
|
*
|
|
5531
|
-
* @
|
|
5532
|
-
*
|
|
5533
|
-
*
|
|
5534
|
-
*
|
|
5535
|
-
*
|
|
5536
|
-
*
|
|
5537
|
-
*
|
|
5538
|
-
* @public
|
|
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.
|
|
5539
5920
|
*/
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
#measured;
|
|
5545
|
-
/**
|
|
5546
|
-
* Initializes a new instance of the Measurement class.
|
|
5547
|
-
*
|
|
5548
|
-
* @param provider - The telemetry provider responsible for handling telemetry data.
|
|
5549
|
-
* @param data - The measurement data to be associated with this instance.
|
|
5550
|
-
*/
|
|
5551
|
-
constructor(provider, data) {
|
|
5552
|
-
this.#data = data;
|
|
5553
|
-
this.#provider = provider;
|
|
5554
|
-
this.#startTime = performance.now();
|
|
5555
|
-
}
|
|
5556
|
-
/**
|
|
5557
|
-
* Creates a new `Measurement` instance that is a copy of the current one.
|
|
5558
|
-
*
|
|
5559
|
-
* @param resetOptions - Options to control which properties are preserved in the clone.
|
|
5560
|
-
* - `preserveStartTime`: If `true`, the start time of the measurement is preserved in the clone.
|
|
5561
|
-
* @returns A new `Measurement` instance with copied data and optionally preserved start time.
|
|
5562
|
-
*/
|
|
5563
|
-
clone(resetOptions = {}) {
|
|
5564
|
-
const clonedMeasurement = new Measurement(this.#provider, this.#data);
|
|
5565
|
-
// Preserve the original start time when requested, otherwise the clone starts fresh
|
|
5566
|
-
if (resetOptions.preserveStartTime) {
|
|
5567
|
-
clonedMeasurement.#startTime = this.#startTime;
|
|
5568
|
-
}
|
|
5569
|
-
return clonedMeasurement;
|
|
5570
|
-
}
|
|
5571
|
-
/**
|
|
5572
|
-
* Resets the measurement state.
|
|
5573
|
-
*
|
|
5574
|
-
* @param options - Optional settings for the reset operation.
|
|
5575
|
-
* @param options.preserveStartTime - If true, the start time will not be reset; otherwise, it will be set to the current time.
|
|
5576
|
-
*
|
|
5577
|
-
* Resets the measured flag and, unless `preserveStartTime` is true, updates the start time to the current performance time.
|
|
5578
|
-
*/
|
|
5579
|
-
reset(options) {
|
|
5580
|
-
// Keep the existing start time when preserving, otherwise restart the clock
|
|
5581
|
-
if (!options?.preserveStartTime) {
|
|
5582
|
-
this.#startTime = performance.now();
|
|
5583
|
-
}
|
|
5584
|
-
this.#measured = false;
|
|
5585
|
-
}
|
|
5586
|
-
/**
|
|
5587
|
-
* Measures the elapsed time since the last start time and sends the measurement data
|
|
5588
|
-
* to the telemetry provider. Optionally marks the measurement as completed and/or
|
|
5589
|
-
* resets the start time for subsequent measurements.
|
|
5590
|
-
*
|
|
5591
|
-
* @param data - Optional additional measurement data to merge with the existing data.
|
|
5592
|
-
* @param options - Optional measurement options.
|
|
5593
|
-
* @param options.markAsMeasured - If true, marks this measurement as completed.
|
|
5594
|
-
* @param options.resetStartTime - If true, resets the start time after measuring.
|
|
5595
|
-
* @returns The elapsed duration in milliseconds since the last start time.
|
|
5596
|
-
*/
|
|
5597
|
-
measure(data, options) {
|
|
5598
|
-
const measuredData = mergeTelemetryItem(this.#data, data ?? {});
|
|
5599
|
-
const duration = performance.now() - this.#startTime;
|
|
5600
|
-
this.#provider.trackMetric({
|
|
5601
|
-
...measuredData,
|
|
5602
|
-
value: duration,
|
|
5603
|
-
});
|
|
5604
|
-
// Mark as measured only when explicitly requested by the caller
|
|
5605
|
-
if (options?.markAsMeasured) {
|
|
5606
|
-
this.#measured = true;
|
|
5607
|
-
}
|
|
5608
|
-
// Restart the clock only when explicitly requested by the caller
|
|
5609
|
-
if (options?.resetStartTime) {
|
|
5610
|
-
this.#startTime = performance.now();
|
|
5611
|
-
}
|
|
5612
|
-
return duration;
|
|
5613
|
-
}
|
|
5614
|
-
/**
|
|
5615
|
-
* Resolves a given promise, measures the result using the provided options, and returns the resolved value.
|
|
5616
|
-
*
|
|
5617
|
-
* @template T - The type of the resolved value from the promise.
|
|
5618
|
-
* @param promise - The promise to resolve.
|
|
5619
|
-
* @param options - Optional configuration for resolving and measuring:
|
|
5620
|
-
* - `data`: A value or a function that receives the resolved result and returns measurement data.
|
|
5621
|
-
* - `options`: Additional options to pass to the `measure` method.
|
|
5622
|
-
* @returns A promise that resolves to the value of the input promise.
|
|
5623
|
-
*/
|
|
5624
|
-
async resolve(promise, options) {
|
|
5625
|
-
const result = await promise;
|
|
5626
|
-
const endData = typeof options?.data === 'function' ? options?.data(result) : options?.data;
|
|
5627
|
-
this.measure(await Promise.resolve(endData), options?.options);
|
|
5628
|
-
return result;
|
|
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;
|
|
5629
5925
|
}
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
*
|
|
5635
|
-
* @template T - The return type of the function to execute.
|
|
5636
|
-
* @param fn - A function that returns a value or a Promise to be resolved.
|
|
5637
|
-
* @param options - Optional resolution options to customize the resolve behavior.
|
|
5638
|
-
* @returns A Promise that resolves to the result of the executed function.
|
|
5639
|
-
*/
|
|
5640
|
-
exec(fn, options) {
|
|
5641
|
-
return this.resolve(Promise.resolve(fn()), options);
|
|
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;
|
|
5642
5930
|
}
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
* This method is intended to be used with the ECMAScript `Symbol.dispose` protocol,
|
|
5648
|
-
* allowing for automatic resource management when used in a `using` statement.
|
|
5649
|
-
*
|
|
5650
|
-
* @see https://github.com/tc39/proposal-explicit-resource-management
|
|
5651
|
-
*/
|
|
5652
|
-
[Symbol.dispose]() {
|
|
5653
|
-
// Ensure a measurement is always taken before the instance is discarded
|
|
5654
|
-
if (!this.#measured) {
|
|
5655
|
-
try {
|
|
5656
|
-
this.measure();
|
|
5657
|
-
}
|
|
5658
|
-
catch {
|
|
5659
|
-
// Silently handle errors during disposal to ensure cleanup always completes
|
|
5660
|
-
// The measurement failure should not prevent proper resource disposal
|
|
5661
|
-
}
|
|
5662
|
-
}
|
|
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;
|
|
5663
5935
|
}
|
|
5936
|
+
return false;
|
|
5664
5937
|
}
|
|
5665
5938
|
|
|
5666
5939
|
/**
|
|
@@ -5716,67 +5989,643 @@ const applyMetadata = (metadata, args) => {
|
|
|
5716
5989
|
};
|
|
5717
5990
|
|
|
5718
5991
|
/**
|
|
5719
|
-
*
|
|
5720
|
-
*
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
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
|
+
}
|
|
6582
|
+
|
|
6583
|
+
/**
|
|
6584
|
+
* The telemetry provider applications and modules use to track events, exceptions, metrics
|
|
6585
|
+
* and custom telemetry.
|
|
5725
6586
|
*
|
|
5726
|
-
*
|
|
5727
|
-
*
|
|
5728
|
-
*
|
|
5729
|
-
* - Allows relaying telemetry data to a parent provider, merging metadata as needed.
|
|
5730
|
-
* - Provides utility methods for tracking and measuring telemetry items.
|
|
5731
|
-
* - Handles error reporting via telemetry error events.
|
|
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.
|
|
5732
6590
|
*
|
|
5733
6591
|
* @example
|
|
5734
6592
|
* ```typescript
|
|
5735
6593
|
* const provider = new TelemetryProvider(config, { event: eventProvider });
|
|
5736
|
-
* provider.
|
|
6594
|
+
* await provider.initialize({ parent });
|
|
6595
|
+
* provider.trackEvent({ name: 'user_login', metadata: { userId: '123' } });
|
|
5737
6596
|
* const end = provider.measure({ name: 'load_time' });
|
|
5738
6597
|
* // ... do work ...
|
|
5739
6598
|
* end();
|
|
6599
|
+
* await provider.dispose();
|
|
5740
6600
|
* ```
|
|
5741
|
-
*
|
|
5742
|
-
* @typeParam TelemetryConfig - The configuration type for telemetry.
|
|
5743
|
-
* @implements ITelemetryProvider
|
|
5744
|
-
* @extends BaseModuleProvider<TelemetryConfig>
|
|
5745
6601
|
*/
|
|
5746
6602
|
class TelemetryProvider extends BaseModuleProvider {
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
#
|
|
5750
|
-
#adapters = {};
|
|
5751
|
-
#handledByChild = new WeakSet();
|
|
5752
|
-
#filters;
|
|
5753
|
-
#defaultScope;
|
|
5754
|
-
#eventProvider;
|
|
6603
|
+
/** Immutable composition-root origin used by optional infrastructure reporters. */
|
|
6604
|
+
source;
|
|
6605
|
+
#state;
|
|
5755
6606
|
/**
|
|
5756
6607
|
* Stream of every telemetry item tracked by this provider.
|
|
5757
6608
|
*
|
|
5758
6609
|
* @returns An observable emitting each tracked `TelemetryItem`.
|
|
5759
6610
|
*/
|
|
5760
6611
|
get items() {
|
|
5761
|
-
return this.#items
|
|
6612
|
+
return this.#state.items;
|
|
5762
6613
|
}
|
|
5763
6614
|
/**
|
|
5764
|
-
* Returns true
|
|
6615
|
+
* Returns true once initialization has started.
|
|
5765
6616
|
*
|
|
5766
|
-
* @returns `true`
|
|
6617
|
+
* @returns `true` once {@link initialize} has been called, otherwise `false`.
|
|
5767
6618
|
*/
|
|
5768
6619
|
get initialized() {
|
|
5769
|
-
return this.#
|
|
6620
|
+
return this.#state.value.status !== 'idle';
|
|
5770
6621
|
}
|
|
5771
|
-
#metadata;
|
|
5772
|
-
#modules;
|
|
5773
6622
|
/**
|
|
5774
6623
|
* Sets the resolved framework module instances used to resolve metadata.
|
|
5775
6624
|
*
|
|
5776
6625
|
* @param value - The resolved framework module instances.
|
|
5777
6626
|
*/
|
|
5778
6627
|
set modules(value) {
|
|
5779
|
-
this.#modules = value;
|
|
6628
|
+
this.#state.modules = value;
|
|
5780
6629
|
}
|
|
5781
6630
|
/**
|
|
5782
6631
|
* Creates a new `TelemetryProvider`.
|
|
@@ -5787,51 +6636,27 @@ class TelemetryProvider extends BaseModuleProvider {
|
|
|
5787
6636
|
*/
|
|
5788
6637
|
constructor(config, deps) {
|
|
5789
6638
|
super({ version: version$2, config });
|
|
5790
|
-
this
|
|
5791
|
-
this.#
|
|
5792
|
-
this.#defaultScope = config?.defaultScope ?? [];
|
|
5793
|
-
this.#eventProvider = deps?.event;
|
|
5794
|
-
this.#filters = config?.filter;
|
|
6639
|
+
this.source = Object.freeze(config?.source ?? { hostId: crypto.randomUUID() });
|
|
6640
|
+
this.#state = new TelemetryProviderState({ config, event: deps?.event, owner: this });
|
|
5795
6641
|
}
|
|
5796
6642
|
/**
|
|
5797
|
-
*
|
|
6643
|
+
* Connects exporters, the parent relay and startup streams. Exporter initialization failures
|
|
6644
|
+
* are reported but do not prevent the provider from becoming usable.
|
|
5798
6645
|
*
|
|
5799
|
-
*
|
|
5800
|
-
* 1. Storing the provided adapters
|
|
5801
|
-
* 2. Initializing all adapters that support async initialization (failures are logged but don't prevent initialization)
|
|
5802
|
-
* 3. Setting up subscriptions for telemetry processing
|
|
5803
|
-
* 4. Handling initial telemetry items if provided
|
|
6646
|
+
* Repeated and reentrant calls return the same promise.
|
|
5804
6647
|
*
|
|
5805
|
-
* @param args -
|
|
5806
|
-
* @
|
|
5807
|
-
* @
|
|
5808
|
-
* @param args.initialItems - Optional observable of initial telemetry items to process
|
|
5809
|
-
* @returns A promise that resolves when initialization is complete
|
|
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.
|
|
5810
6651
|
*/
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
// If a parent telemetry provider is provided, relay telemetry data to it
|
|
5820
|
-
if (parent) {
|
|
5821
|
-
this._addTeardown(this._relayTelemetryData(parent));
|
|
5822
|
-
}
|
|
5823
|
-
// Emit telemetry items as events if an event provider is available
|
|
5824
|
-
this._addTeardown(this.#items.subscribe(this._dispatchEntityEvent.bind(this)));
|
|
5825
|
-
// Handle initial telemetry items if provided
|
|
5826
|
-
if (initialItems) {
|
|
5827
|
-
this._addTeardown(from(initialItems).subscribe((item) => {
|
|
5828
|
-
this.track(item);
|
|
5829
|
-
}));
|
|
5830
|
-
}
|
|
5831
|
-
// Ensure the subject is completed when the provider is destroyed
|
|
5832
|
-
this._addTeardown(() => {
|
|
5833
|
-
this.#items.complete();
|
|
5834
|
-
});
|
|
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;
|
|
5835
6660
|
}
|
|
5836
6661
|
/**
|
|
5837
6662
|
* Stops telemetry subscriptions and waits for every adapter to flush and shut down.
|
|
@@ -5847,158 +6672,7 @@ class TelemetryProvider extends BaseModuleProvider {
|
|
|
5847
6672
|
* ```
|
|
5848
6673
|
*/
|
|
5849
6674
|
dispose() {
|
|
5850
|
-
|
|
5851
|
-
if (this.#disposal) {
|
|
5852
|
-
return this.#disposal;
|
|
5853
|
-
}
|
|
5854
|
-
super.dispose();
|
|
5855
|
-
// Defer adapter callbacks until the shared promise is assigned, including reentrant disposal.
|
|
5856
|
-
this.#disposal = Promise.resolve().then(async () => {
|
|
5857
|
-
// Shut down all exporters even when one rejects or throws synchronously.
|
|
5858
|
-
const results = await Promise.allSettled(Object.entries(this.#adapters).map(async ([identifier, adapter]) => {
|
|
5859
|
-
try {
|
|
5860
|
-
await adapter.shutdown?.();
|
|
5861
|
-
}
|
|
5862
|
-
catch (cause) {
|
|
5863
|
-
throw new Error(`Failed to shut down telemetry adapter "${identifier}"`, { cause });
|
|
5864
|
-
}
|
|
5865
|
-
}));
|
|
5866
|
-
const errors = [];
|
|
5867
|
-
// Retain each failure for the module lifecycle's structured disposal error event.
|
|
5868
|
-
for (const result of results) {
|
|
5869
|
-
// Successful shutdowns must not obscure another exporter's failure.
|
|
5870
|
-
if (result.status === 'rejected') {
|
|
5871
|
-
errors.push(result.reason);
|
|
5872
|
-
}
|
|
5873
|
-
}
|
|
5874
|
-
// Reject only after all exporters have had a chance to release their resources.
|
|
5875
|
-
if (errors.length) {
|
|
5876
|
-
throw new AggregateError(errors, 'Failed to shut down telemetry adapters');
|
|
5877
|
-
}
|
|
5878
|
-
});
|
|
5879
|
-
return this.#disposal;
|
|
5880
|
-
}
|
|
5881
|
-
/**
|
|
5882
|
-
* Initializes all telemetry adapters that support async initialization.
|
|
5883
|
-
* Uses Promise.allSettled to ensure all adapters attempt initialization,
|
|
5884
|
-
* with individual error reporting for any failures.
|
|
5885
|
-
* Subclasses can override this method to customize adapter initialization behavior.
|
|
5886
|
-
*
|
|
5887
|
-
* @protected
|
|
5888
|
-
*/
|
|
5889
|
-
async _initializeAdapters() {
|
|
5890
|
-
// Initialize all adapters, do it in parallel
|
|
5891
|
-
const adapterEntries = Object.entries(this.#adapters);
|
|
5892
|
-
// Kick off every adapter's initialize() concurrently
|
|
5893
|
-
const initializationPromises = adapterEntries.map(([_identifier, adapter]) => adapter.initialize());
|
|
5894
|
-
// Wait for all adapters to settle (either resolve or reject)
|
|
5895
|
-
const results = await Promise.allSettled(initializationPromises);
|
|
5896
|
-
// Check each result and dispatch errors for failed initializations
|
|
5897
|
-
for (const [index, result] of results.entries()) {
|
|
5898
|
-
// Only rejected adapters need an error dispatched; fulfilled ones need no action
|
|
5899
|
-
if (result.status === 'rejected') {
|
|
5900
|
-
const [identifier] = adapterEntries[index];
|
|
5901
|
-
this._dispatchError(new Error(`Failed to initialize telemetry adapter "${identifier}"`, {
|
|
5902
|
-
cause: result.reason,
|
|
5903
|
-
}));
|
|
5904
|
-
}
|
|
5905
|
-
}
|
|
5906
|
-
}
|
|
5907
|
-
/**
|
|
5908
|
-
* Subscribes all adapters to the telemetry item stream.
|
|
5909
|
-
* Each adapter processes every telemetry item as it is emitted.
|
|
5910
|
-
* If an adapter filter is configured, only items that pass the filter will be sent to adapters.
|
|
5911
|
-
* If an adapter throws, the error is dispatched as a telemetry error event.
|
|
5912
|
-
*
|
|
5913
|
-
* @returns Subscription to the telemetry item stream
|
|
5914
|
-
* @protected
|
|
5915
|
-
* @throws {Error} When the provider has not been initialized yet.
|
|
5916
|
-
*/
|
|
5917
|
-
_connectAdapters() {
|
|
5918
|
-
// Adapters can only be connected after the provider has completed initialization
|
|
5919
|
-
if (!this.#initialized) {
|
|
5920
|
-
throw new Error('TelemetryProvider is not initialized');
|
|
5921
|
-
}
|
|
5922
|
-
return this.#items.subscribe((item) => {
|
|
5923
|
-
const { adapter: adapterFilter } = this.#filters ?? {};
|
|
5924
|
-
// Apply adapter filter if configured - skip item if filter returns false
|
|
5925
|
-
if (adapterFilter && !adapterFilter(item)) {
|
|
5926
|
-
return;
|
|
5927
|
-
}
|
|
5928
|
-
// Iterate through all registered adapters
|
|
5929
|
-
for (const [identifier, adapter] of Object.entries(this.#adapters)) {
|
|
5930
|
-
// Explicit child exporters take precedence over inherited development exporters.
|
|
5931
|
-
if (adapter.fallback && this.#handledByChild.has(item)) {
|
|
5932
|
-
// Keep application-owned records out of parent fallback exporters.
|
|
5933
|
-
continue;
|
|
5934
|
-
}
|
|
5935
|
-
try {
|
|
5936
|
-
// Let the adapter process the telemetry item
|
|
5937
|
-
Promise.resolve(adapter.processItem(item));
|
|
5938
|
-
}
|
|
5939
|
-
catch (error) {
|
|
5940
|
-
// If processing fails, dispatch an error event
|
|
5941
|
-
this._dispatchError(new Error(`Failed to process telemetry item with adapter "${identifier}"`, {
|
|
5942
|
-
cause: error,
|
|
5943
|
-
}));
|
|
5944
|
-
}
|
|
5945
|
-
}
|
|
5946
|
-
});
|
|
5947
|
-
}
|
|
5948
|
-
/**
|
|
5949
|
-
* Relays telemetry data items to a target telemetry provider.
|
|
5950
|
-
*
|
|
5951
|
-
* Subscribes to the internal telemetry item stream and forwards each processed item
|
|
5952
|
-
* to the specified target provider by invoking its `track` method.
|
|
5953
|
-
* If a relay filter is configured, only items that pass the filter will be relayed.
|
|
5954
|
-
*
|
|
5955
|
-
* @param target - The telemetry provider to which telemetry data should be relayed.
|
|
5956
|
-
* @returns A Subscription object that can be used to unsubscribe from the relay.
|
|
5957
|
-
*/
|
|
5958
|
-
_relayTelemetryData(target) {
|
|
5959
|
-
// Subscribe to the processed stream and forward each item to the parent provider
|
|
5960
|
-
return this.#items.subscribe((item) => {
|
|
5961
|
-
const { relay: relayFilter } = this.#filters ?? {};
|
|
5962
|
-
// Apply relay filter if configured - skip item if filter returns false
|
|
5963
|
-
if (relayFilter && !relayFilter(item)) {
|
|
5964
|
-
return;
|
|
5965
|
-
}
|
|
5966
|
-
// Older/custom providers retain the original track-only relay contract.
|
|
5967
|
-
if (target.trackRelayed) {
|
|
5968
|
-
// Carry exporter ownership across intermediate providers without changing record metadata.
|
|
5969
|
-
const hasAdapters = this.#handledByChild.has(item) ||
|
|
5970
|
-
Object.values(this.#adapters).some((adapter) => !adapter.fallback);
|
|
5971
|
-
target.trackRelayed(item, hasAdapters);
|
|
5972
|
-
}
|
|
5973
|
-
else {
|
|
5974
|
-
target.track(item);
|
|
5975
|
-
}
|
|
5976
|
-
});
|
|
5977
|
-
}
|
|
5978
|
-
/**
|
|
5979
|
-
* Dispatches a telemetry event to the event provider, if available.
|
|
5980
|
-
*
|
|
5981
|
-
* @param item - The telemetry item to dispatch as an event
|
|
5982
|
-
* @protected
|
|
5983
|
-
*/
|
|
5984
|
-
_dispatchEntityEvent(item) {
|
|
5985
|
-
// Only dispatch when an event provider is configured
|
|
5986
|
-
if (this.#eventProvider) {
|
|
5987
|
-
// Wrap the telemetry item in a TelemetryEvent and dispatch it
|
|
5988
|
-
this.#eventProvider.dispatchEvent(new TelemetryEvent(item, this));
|
|
5989
|
-
}
|
|
5990
|
-
}
|
|
5991
|
-
/**
|
|
5992
|
-
* Dispatches a telemetry error event using the internal event provider, if available.
|
|
5993
|
-
*
|
|
5994
|
-
* @param error - The error instance to be dispatched with the telemetry error event.
|
|
5995
|
-
* @protected
|
|
5996
|
-
*/
|
|
5997
|
-
_dispatchError(error) {
|
|
5998
|
-
// Only dispatch when an event provider is configured
|
|
5999
|
-
if (this.#eventProvider) {
|
|
6000
|
-
this.#eventProvider.dispatchEvent(new TelemetryErrorEvent(error, this));
|
|
6001
|
-
}
|
|
6675
|
+
return this.#state.dispose();
|
|
6002
6676
|
}
|
|
6003
6677
|
/**
|
|
6004
6678
|
* Tracks a telemetry item by type, merging the default scope with the provided scope.
|
|
@@ -6036,44 +6710,7 @@ class TelemetryProvider extends BaseModuleProvider {
|
|
|
6036
6710
|
* @param hasAdapters - Whether an explicit child adapter has already handled this record.
|
|
6037
6711
|
*/
|
|
6038
6712
|
trackRelayed(item, hasAdapters) {
|
|
6039
|
-
this.
|
|
6040
|
-
}
|
|
6041
|
-
/**
|
|
6042
|
-
* Tracks a telemetry item by merging the default scope with the item's scope (if provided),
|
|
6043
|
-
* ensuring that all telemetry items have the correct scope context. The merged item is then
|
|
6044
|
-
* emitted to downstream handlers.
|
|
6045
|
-
*
|
|
6046
|
-
* @param item - The telemetry item to be tracked, conforming to the input schema of `TelemetryItemSchema`.
|
|
6047
|
-
* @param handledByChild - Whether fallback adapters should skip this relayed item.
|
|
6048
|
-
*
|
|
6049
|
-
* @protected
|
|
6050
|
-
*/
|
|
6051
|
-
async _track(item, handledByChild = false) {
|
|
6052
|
-
const mergedItem = mergeTelemetryItem(item, {
|
|
6053
|
-
scope: this.#defaultScope,
|
|
6054
|
-
});
|
|
6055
|
-
// Skip metadata application for TelemetryMetadataError items to prevent infinite recursion
|
|
6056
|
-
if (this.#metadata && mergedItem.name !== TelemetryItemNames.MetadataError) {
|
|
6057
|
-
applyMetadata(this.#metadata, {
|
|
6058
|
-
modules: this.#modules,
|
|
6059
|
-
item: mergedItem,
|
|
6060
|
-
}).subscribe((nextItem) => {
|
|
6061
|
-
// Async metadata resolution creates new objects; retain ownership on the emitted record.
|
|
6062
|
-
if (handledByChild) {
|
|
6063
|
-
this.#handledByChild.add(nextItem);
|
|
6064
|
-
}
|
|
6065
|
-
// Emit the item after applying metadata
|
|
6066
|
-
this.#items.next(nextItem);
|
|
6067
|
-
});
|
|
6068
|
-
}
|
|
6069
|
-
else {
|
|
6070
|
-
// Providers without metadata still need the same fallback precedence.
|
|
6071
|
-
if (handledByChild) {
|
|
6072
|
-
this.#handledByChild.add(mergedItem);
|
|
6073
|
-
}
|
|
6074
|
-
// If no metadata extractor is provided or item is TelemetryMetadataError, emit directly
|
|
6075
|
-
this.#items.next(mergedItem);
|
|
6076
|
-
}
|
|
6713
|
+
this.#state.track(item, hasAdapters);
|
|
6077
6714
|
}
|
|
6078
6715
|
/**
|
|
6079
6716
|
* Starts a metric measurement and returns a function to end the measurement.
|
|
@@ -6099,7 +6736,7 @@ class TelemetryProvider extends BaseModuleProvider {
|
|
|
6099
6736
|
* provider.trackEvent({ name: 'button_click', metadata: { button: 'save' } });
|
|
6100
6737
|
*/
|
|
6101
6738
|
trackEvent(data) {
|
|
6102
|
-
this.
|
|
6739
|
+
this.#state.track(TelemetryEventSchema.parse({ ...data, type: TelemetryType.Event }));
|
|
6103
6740
|
}
|
|
6104
6741
|
/**
|
|
6105
6742
|
* Tracks a telemetry exception.
|
|
@@ -6113,7 +6750,7 @@ class TelemetryProvider extends BaseModuleProvider {
|
|
|
6113
6750
|
* });
|
|
6114
6751
|
*/
|
|
6115
6752
|
trackException(data) {
|
|
6116
|
-
this.
|
|
6753
|
+
this.#state.track(TelemetryExceptionSchema.parse({ ...data, type: TelemetryType.Exception }));
|
|
6117
6754
|
}
|
|
6118
6755
|
/**
|
|
6119
6756
|
* Tracks a telemetry metric.
|
|
@@ -6123,7 +6760,7 @@ class TelemetryProvider extends BaseModuleProvider {
|
|
|
6123
6760
|
* provider.trackMetric({ name: 'response_time', value: 123 });
|
|
6124
6761
|
*/
|
|
6125
6762
|
trackMetric(data) {
|
|
6126
|
-
this.
|
|
6763
|
+
this.#state.track(TelemetryMetricSchema.parse({ ...data, type: TelemetryType.Metric }));
|
|
6127
6764
|
}
|
|
6128
6765
|
/**
|
|
6129
6766
|
* Tracks a custom telemetry event.
|
|
@@ -6133,7 +6770,7 @@ class TelemetryProvider extends BaseModuleProvider {
|
|
|
6133
6770
|
* provider.trackCustom({ name: 'custom_event', metadata: { foo: 'bar' } });
|
|
6134
6771
|
*/
|
|
6135
6772
|
trackCustom(data) {
|
|
6136
|
-
this.
|
|
6773
|
+
this.#state.track(TelemetryCustomEventSchema.parse({ ...data, type: TelemetryType.Custom }));
|
|
6137
6774
|
}
|
|
6138
6775
|
/**
|
|
6139
6776
|
* Retrieves a telemetry adapter by its identifier.
|
|
@@ -6143,7 +6780,7 @@ class TelemetryProvider extends BaseModuleProvider {
|
|
|
6143
6780
|
* @returns The telemetry adapter corresponding to the given identifier, or `undefined` if not found.
|
|
6144
6781
|
*/
|
|
6145
6782
|
getAdapter(identifier) {
|
|
6146
|
-
const adapter = this.#adapters[identifier];
|
|
6783
|
+
const adapter = this.#state.adapters[identifier];
|
|
6147
6784
|
// Report a missing adapter as a telemetry exception rather than throwing
|
|
6148
6785
|
if (!adapter) {
|
|
6149
6786
|
this.trackException({
|
|
@@ -6162,10 +6799,17 @@ class TelemetryProvider extends BaseModuleProvider {
|
|
|
6162
6799
|
* @returns True if the adapter exists, false otherwise.
|
|
6163
6800
|
*/
|
|
6164
6801
|
hasAdapter(identifier) {
|
|
6165
|
-
return identifier in this.#adapters;
|
|
6802
|
+
return identifier in this.#state.adapters;
|
|
6166
6803
|
}
|
|
6167
6804
|
}
|
|
6168
6805
|
|
|
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
|
+
}
|
|
6169
6813
|
/**
|
|
6170
6814
|
* Telemetry module definition for the Fusion Framework.
|
|
6171
6815
|
*
|
|
@@ -6183,7 +6827,9 @@ class TelemetryProvider extends BaseModuleProvider {
|
|
|
6183
6827
|
*/
|
|
6184
6828
|
const module$2 = {
|
|
6185
6829
|
name: 'telemetry',
|
|
6186
|
-
|
|
6830
|
+
disposePhase: 'diagnostics',
|
|
6831
|
+
prepare: createTelemetryConfigurator,
|
|
6832
|
+
configure: createTelemetryConfigurator,
|
|
6187
6833
|
initialize: async (args) => {
|
|
6188
6834
|
const config = await args.config.createConfigAsync(args);
|
|
6189
6835
|
const event = args.hasModule('event') ? await args.requireInstance('event') : undefined;
|
|
@@ -6206,69 +6852,399 @@ const module$2 = {
|
|
|
6206
6852
|
};
|
|
6207
6853
|
|
|
6208
6854
|
/**
|
|
6209
|
-
*
|
|
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.
|
|
6210
6948
|
*
|
|
6211
|
-
*
|
|
6212
|
-
*
|
|
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.
|
|
6213
6952
|
*
|
|
6214
|
-
*
|
|
6215
|
-
*
|
|
6216
|
-
*
|
|
6217
|
-
*
|
|
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.
|
|
6218
7013
|
*
|
|
6219
|
-
*
|
|
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
|
+
* ```
|
|
6220
7029
|
*/
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
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();
|
|
6242
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();
|
|
7092
|
+
},
|
|
7093
|
+
});
|
|
7094
|
+
// Synchronous replay can close capture before the subscription is assigned.
|
|
7095
|
+
if (this.#status === 'closed') {
|
|
7096
|
+
this.#subscription.unsubscribe();
|
|
6243
7097
|
}
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
};
|
|
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;
|
|
6251
7104
|
}
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
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 });
|
|
7201
|
+
}
|
|
7202
|
+
catch {
|
|
7203
|
+
// Parent delivery failures fall through to local health reporting.
|
|
7204
|
+
return false;
|
|
7205
|
+
}
|
|
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
|
+
}
|
|
6255
7230
|
|
|
6256
7231
|
/**
|
|
6257
7232
|
* Enables telemetry for a given module configurator.
|
|
6258
7233
|
*
|
|
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
|
+
*
|
|
6259
7238
|
* @param configurator - The module configurator instance to which telemetry should be attached.
|
|
6260
7239
|
* @param options - Optional configuration for telemetry setup.
|
|
6261
|
-
* @param options.attachConfiguratorEvents -
|
|
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.
|
|
6262
7243
|
* @param options.configure - An optional callback to further configure the telemetry builder. Can be synchronous or asynchronous.
|
|
6263
7244
|
*
|
|
6264
7245
|
* @remarks
|
|
6265
|
-
*
|
|
6266
|
-
*
|
|
6267
|
-
*
|
|
6268
|
-
* @remarks
|
|
6269
|
-
* `attachConfiguratorEvents` allows for the automatic attachment of configurator events to the telemetry builder,
|
|
6270
|
-
* ensuring that relevant events are captured and processed. __NOTE__ if the configurator crashes before the telemetry
|
|
6271
|
-
* builder is fully initialized, some events may be missed.
|
|
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.
|
|
6272
7248
|
*
|
|
6273
7249
|
* @example
|
|
6274
7250
|
* ```typescript
|
|
@@ -6288,18 +7264,19 @@ configurator) => {
|
|
|
6288
7264
|
const enableTelemetry = (
|
|
6289
7265
|
// biome-ignore lint/suspicious/noExplicitAny: must be any to support all module types
|
|
6290
7266
|
configurator, options) => {
|
|
7267
|
+
const attach = options?.attachConfiguratorEvents;
|
|
6291
7268
|
configurator.addConfig({
|
|
6292
7269
|
module: module$2,
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
builder.
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
}
|
|
6302
|
-
|
|
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,
|
|
6303
7280
|
});
|
|
6304
7281
|
};
|
|
6305
7282
|
|
|
@@ -8633,6 +9610,9 @@ class ServiceDiscoveryClient {
|
|
|
8633
9610
|
#query;
|
|
8634
9611
|
#failure;
|
|
8635
9612
|
#lastKnownGood;
|
|
9613
|
+
#diagnostic = new Subject();
|
|
9614
|
+
/** {@inheritDoc IServiceDiscoveryClient.diagnostic$} */
|
|
9615
|
+
diagnostic$ = this.#diagnostic.asObservable();
|
|
8636
9616
|
/** Sub-path appended to the HTTP client's base URI when fetching services. */
|
|
8637
9617
|
endpoint;
|
|
8638
9618
|
/** HTTP client used to call the service discovery API. */
|
|
@@ -8659,32 +9639,40 @@ class ServiceDiscoveryClient {
|
|
|
8659
9639
|
}
|
|
8660
9640
|
/** {@inheritDoc IServiceDiscoveryClient.resolveServices} */
|
|
8661
9641
|
async resolveServices(allow_cache) {
|
|
9642
|
+
const started = performance.now();
|
|
8662
9643
|
// Fail fast while cooling down so retry/reload/render loops never reach the network
|
|
8663
9644
|
if (this.#failure && Date.now() < this.#failure.retryAt) {
|
|
8664
9645
|
return this.#resolveFallback(new ServiceDiscoveryUnavailableError({
|
|
8665
9646
|
cause: this.#failure.error,
|
|
8666
9647
|
retryAt: this.#failure.retryAt,
|
|
8667
9648
|
attempt: this.#failure.attempt,
|
|
8668
|
-
}));
|
|
9649
|
+
}), started, 'cooldown');
|
|
8669
9650
|
}
|
|
8670
9651
|
const startedAt = Date.now();
|
|
9652
|
+
let record;
|
|
8671
9653
|
try {
|
|
8672
9654
|
// firstValueFrom returns the cached snapshot; lastValueFrom waits for
|
|
8673
9655
|
// the freshest value from the query
|
|
8674
9656
|
const fn = allow_cache ? firstValueFrom : lastValueFrom;
|
|
8675
|
-
|
|
8676
|
-
// Only a fresh response proves discovery works; a cached (possibly expired) snapshot must
|
|
8677
|
-
// not reset the failure streak or renew the age of the fallback list
|
|
8678
|
-
if (record.status === 'complete') {
|
|
8679
|
-
this.#failure = undefined;
|
|
8680
|
-
this.#lastKnownGood = { services: record.value, receivedAt: Date.now() };
|
|
8681
|
-
}
|
|
8682
|
-
return record.value;
|
|
9657
|
+
record = await fn(this.#query.query());
|
|
8683
9658
|
}
|
|
8684
9659
|
catch (error) {
|
|
8685
9660
|
this.#registerFailure(error, startedAt);
|
|
8686
|
-
return this.#resolveFallback(error);
|
|
8687
|
-
}
|
|
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;
|
|
8688
9676
|
}
|
|
8689
9677
|
/**
|
|
8690
9678
|
* Records a failed request and schedules the next allowed request time.
|
|
@@ -8717,17 +9705,43 @@ class ServiceDiscoveryClient {
|
|
|
8717
9705
|
* otherwise rethrows the given error.
|
|
8718
9706
|
*
|
|
8719
9707
|
* @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.
|
|
8720
9710
|
* @returns The last known good service list.
|
|
8721
9711
|
* @throws The provided `error` when no service list younger than 24 hours exists.
|
|
8722
9712
|
*/
|
|
8723
|
-
#resolveFallback(error) {
|
|
9713
|
+
#resolveFallback(error, started, failedOrigin) {
|
|
8724
9714
|
const lastKnownGood = this.#lastKnownGood;
|
|
8725
9715
|
// Prefer a recent service list over an error so callers have nothing to retry
|
|
8726
9716
|
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
|
+
});
|
|
8727
9723
|
return lastKnownGood.services;
|
|
8728
9724
|
}
|
|
9725
|
+
this.#emit({
|
|
9726
|
+
type: 'resolve',
|
|
9727
|
+
outcome: 'failure',
|
|
9728
|
+
origin: failedOrigin,
|
|
9729
|
+
duration: performance.now() - started,
|
|
9730
|
+
});
|
|
8729
9731
|
throw error;
|
|
8730
9732
|
}
|
|
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
|
+
}
|
|
8731
9745
|
/** {@inheritDoc IServiceDiscoveryClient.resolveService} */
|
|
8732
9746
|
async resolveService(key, allow_cache) {
|
|
8733
9747
|
const services = await this.resolveServices(allow_cache);
|
|
@@ -8735,6 +9749,7 @@ class ServiceDiscoveryClient {
|
|
|
8735
9749
|
const service = services.find((s) => s.key === key);
|
|
8736
9750
|
// Fail loudly when the requested key doesn't resolve to a known service
|
|
8737
9751
|
if (!service) {
|
|
9752
|
+
this.#emit({ type: 'unknown-service' });
|
|
8738
9753
|
throw Error(`Failed to resolve service, invalid key [${key}]`);
|
|
8739
9754
|
}
|
|
8740
9755
|
return service;
|
|
@@ -8928,7 +9943,7 @@ class ServiceDiscoveryConfigurator extends BaseConfigBuilder {
|
|
|
8928
9943
|
}
|
|
8929
9944
|
|
|
8930
9945
|
// Generated by genversion.
|
|
8931
|
-
const version$1 = '10.
|
|
9946
|
+
const version$1 = '10.3.0-next.0';
|
|
8932
9947
|
|
|
8933
9948
|
/**
|
|
8934
9949
|
* Default implementation of {@link IServiceDiscoveryProvider}.
|
|
@@ -8951,6 +9966,62 @@ class ServiceDiscoveryProvider extends BaseModuleProvider {
|
|
|
8951
9966
|
});
|
|
8952
9967
|
this.config = config;
|
|
8953
9968
|
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 resolution, plus
|
|
9980
|
+
* `cache-hit`, `fallback`, `cooldown` and `unknown-service` notifications. No keys,
|
|
9981
|
+
* URIs, scopes or errors are recorded, and HTTP attempts are left to HTTP telemetry.
|
|
9982
|
+
* Calling it again replaces the provider; `undefined` records nothing until reconnected.
|
|
9983
|
+
*
|
|
9984
|
+
* @param telemetry - Host telemetry provider, or `undefined` to record nothing.
|
|
9985
|
+
*/
|
|
9986
|
+
connectTelemetry(telemetry) {
|
|
9987
|
+
this.#telemetry.connect(telemetry);
|
|
9988
|
+
const diagnostic$ = this.client.diagnostic$;
|
|
9989
|
+
// One subscription serves every connection; custom clients without a stream are skipped.
|
|
9990
|
+
if (this.#diagnostics || !this.#telemetry.connected || !diagnostic$) {
|
|
9991
|
+
return;
|
|
9992
|
+
}
|
|
9993
|
+
const report = this.#telemetry.report;
|
|
9994
|
+
this.#diagnostics = diagnostic$.subscribe((diagnostic) => {
|
|
9995
|
+
// An unmatched key is a notification only; it has no measured resolution of its own.
|
|
9996
|
+
if (diagnostic.type === 'unknown-service') {
|
|
9997
|
+
report({
|
|
9998
|
+
notification: true,
|
|
9999
|
+
operation: 'unknown-service',
|
|
10000
|
+
outcome: 'failure',
|
|
10001
|
+
level: TelemetryLevel.Warning,
|
|
10002
|
+
});
|
|
10003
|
+
return;
|
|
10004
|
+
}
|
|
10005
|
+
const { outcome, origin, duration } = diagnostic;
|
|
10006
|
+
report({ operation: 'resolve', outcome, duration });
|
|
10007
|
+
// Network resolutions are fully described by the measurement and the HTTP attempt.
|
|
10008
|
+
if (origin === 'cache') {
|
|
10009
|
+
report({
|
|
10010
|
+
notification: true,
|
|
10011
|
+
operation: 'cache-hit',
|
|
10012
|
+
outcome,
|
|
10013
|
+
level: TelemetryLevel.Information,
|
|
10014
|
+
});
|
|
10015
|
+
}
|
|
10016
|
+
else if (origin !== 'network') {
|
|
10017
|
+
report({
|
|
10018
|
+
notification: true,
|
|
10019
|
+
operation: origin,
|
|
10020
|
+
outcome,
|
|
10021
|
+
level: TelemetryLevel.Warning,
|
|
10022
|
+
});
|
|
10023
|
+
}
|
|
10024
|
+
});
|
|
8954
10025
|
}
|
|
8955
10026
|
/** {@inheritDoc IServiceDiscoveryProvider.client} */
|
|
8956
10027
|
get client() {
|
|
@@ -8985,6 +10056,11 @@ class ServiceDiscoveryProvider extends BaseModuleProvider {
|
|
|
8985
10056
|
}
|
|
8986
10057
|
}
|
|
8987
10058
|
|
|
10059
|
+
/**
|
|
10060
|
+
* Providers this module created, so host disposal tears down only its own instances and
|
|
10061
|
+
* their telemetry, never a provider supplied or shared by another host.
|
|
10062
|
+
*/
|
|
10063
|
+
const ownedProviders = new WeakMap();
|
|
8988
10064
|
/**
|
|
8989
10065
|
* Module identifier used to register and look up the Service Discovery module
|
|
8990
10066
|
* within the Fusion Framework module system.
|
|
@@ -9025,7 +10101,20 @@ const module$1 = {
|
|
|
9025
10101
|
// service discovery requires http module to be initialized
|
|
9026
10102
|
const httpModule = await requireInstance('http');
|
|
9027
10103
|
// return service discovery provider
|
|
9028
|
-
|
|
10104
|
+
const provider = new ServiceDiscoveryProvider(config, httpModule);
|
|
10105
|
+
ownedProviders.set(provider, provider);
|
|
10106
|
+
return provider;
|
|
10107
|
+
},
|
|
10108
|
+
postInitialize: async ({ instance, modules, ref }) => {
|
|
10109
|
+
// A child reusing its parent's client leaves reporting to the parent, so each
|
|
10110
|
+
// resolution is recorded once, by the host that owns the client.
|
|
10111
|
+
if (instance.client !== ref?.serviceDiscovery?.client) {
|
|
10112
|
+
instance.connectTelemetry?.(modules.telemetry);
|
|
10113
|
+
}
|
|
10114
|
+
},
|
|
10115
|
+
dispose: ({ instance }) => {
|
|
10116
|
+
ownedProviders.get(instance)?.dispose();
|
|
10117
|
+
ownedProviders.delete(instance);
|
|
9029
10118
|
},
|
|
9030
10119
|
};
|
|
9031
10120
|
/**
|
|
@@ -9528,7 +10617,7 @@ async function registerServiceWorker(framework) {
|
|
|
9528
10617
|
}
|
|
9529
10618
|
|
|
9530
10619
|
// Generated by genversion.
|
|
9531
|
-
const version = '5.2.
|
|
10620
|
+
const version = '5.2.4-next.0';
|
|
9532
10621
|
|
|
9533
10622
|
// Allow dynamic import without vite
|
|
9534
10623
|
const importWithoutVite = (path) => import(/* @vite-ignore */ path);
|
|
@@ -9548,7 +10637,7 @@ enableServiceDiscovery(configurator, async (builder) => {
|
|
|
9548
10637
|
});
|
|
9549
10638
|
// Avoid interactive Entra ID authentication when the SPA runs in CI or Playwright.
|
|
9550
10639
|
if (isEnabledEnvValue(import.meta.env.FUSION_SPA_MSAL_MOCK)) {
|
|
9551
|
-
const { enableMsalMock } = await import('../index-
|
|
10640
|
+
const { enableMsalMock } = await import('../index-By7EVum8.js');
|
|
9552
10641
|
const mockServerUrl = import.meta.env.FUSION_SPA_MSAL_MOCK_SERVER_URL;
|
|
9553
10642
|
enableMsalMock(configurator, (builder) => {
|
|
9554
10643
|
// The SPA owns the HTTP adapter; MSAL owns applying the returned token and account.
|
|
@@ -9571,12 +10660,12 @@ else {
|
|
|
9571
10660
|
}
|
|
9572
10661
|
// Initialize tracing before app startup so network requests and logs share active trace context.
|
|
9573
10662
|
if (import.meta.env.FUSION_SPA_TELEMETRY_OTLP_TRACES_ENDPOINT) {
|
|
9574
|
-
const { enableDevServerTracing } = await import('../enable-dev-server-tracing-
|
|
10663
|
+
const { enableDevServerTracing } = await import('../enable-dev-server-tracing-ac1hNKwa.js');
|
|
9575
10664
|
enableDevServerTracing(new URL(import.meta.env.FUSION_SPA_TELEMETRY_OTLP_TRACES_ENDPOINT, window.location.origin).href);
|
|
9576
10665
|
}
|
|
9577
10666
|
// The dev-server environment bridge owns activation; the SPA only consumes its endpoint.
|
|
9578
10667
|
const devServerOtlpAdapter = import.meta.env.FUSION_SPA_TELEMETRY_OTLP_ENDPOINT
|
|
9579
|
-
? new (await import('../index-
|
|
10668
|
+
? new (await import('../index-CYwi54x_.js')).OtlpAdapter({
|
|
9580
10669
|
url: new URL(import.meta.env.FUSION_SPA_TELEMETRY_OTLP_ENDPOINT, window.location.origin).href,
|
|
9581
10670
|
fallback: true,
|
|
9582
10671
|
serviceName: 'fusion.dev-server',
|