@bleedingdev/modern-js-server-runtime-extensions 0.0.0-trusted-publisher-bootstrap

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/dist/cjs/contractGateAutopilot.js +162 -0
  4. package/dist/cjs/contractGateSnapshotStore.js +253 -0
  5. package/dist/cjs/env.js +58 -0
  6. package/dist/cjs/index.js +162 -0
  7. package/dist/cjs/mfCache.js +106 -0
  8. package/dist/cjs/moduleFederationCss.js +285 -0
  9. package/dist/cjs/runtimeFallbackSignal.js +311 -0
  10. package/dist/cjs/telemetry.js +373 -0
  11. package/dist/cjs/telemetryCore.js +819 -0
  12. package/dist/esm/contractGateAutopilot.mjs +124 -0
  13. package/dist/esm/contractGateSnapshotStore.mjs +190 -0
  14. package/dist/esm/env.mjs +17 -0
  15. package/dist/esm/index.mjs +6 -0
  16. package/dist/esm/mfCache.mjs +55 -0
  17. package/dist/esm/moduleFederationCss.mjs +225 -0
  18. package/dist/esm/runtimeFallbackSignal.mjs +222 -0
  19. package/dist/esm/telemetry.mjs +275 -0
  20. package/dist/esm/telemetryCore.mjs +759 -0
  21. package/dist/esm-node/contractGateAutopilot.mjs +125 -0
  22. package/dist/esm-node/contractGateSnapshotStore.mjs +192 -0
  23. package/dist/esm-node/env.mjs +18 -0
  24. package/dist/esm-node/index.mjs +7 -0
  25. package/dist/esm-node/mfCache.mjs +56 -0
  26. package/dist/esm-node/moduleFederationCss.mjs +226 -0
  27. package/dist/esm-node/runtimeFallbackSignal.mjs +223 -0
  28. package/dist/esm-node/telemetry.mjs +276 -0
  29. package/dist/esm-node/telemetryCore.mjs +760 -0
  30. package/dist/types/contractGateAutopilot.d.ts +35 -0
  31. package/dist/types/contractGateSnapshotStore.d.ts +57 -0
  32. package/dist/types/env.d.ts +40 -0
  33. package/dist/types/index.d.ts +6 -0
  34. package/dist/types/mfCache.d.ts +27 -0
  35. package/dist/types/moduleFederationCss.d.ts +87 -0
  36. package/dist/types/runtimeFallbackSignal.d.ts +94 -0
  37. package/dist/types/telemetry.d.ts +12 -0
  38. package/dist/types/telemetryCore.d.ts +257 -0
  39. package/package.json +69 -0
  40. package/rslib.config.mts +4 -0
  41. package/rstest.config.mts +7 -0
  42. package/src/contractGateAutopilot.ts +247 -0
  43. package/src/contractGateSnapshotStore.ts +420 -0
  44. package/src/env.ts +63 -0
  45. package/src/index.ts +84 -0
  46. package/src/mfCache.ts +119 -0
  47. package/src/moduleFederationCss.ts +473 -0
  48. package/src/runtimeFallbackSignal.ts +584 -0
  49. package/src/telemetry.ts +554 -0
  50. package/src/telemetryCore.ts +1332 -0
  51. package/tests/contractGateAutopilot.test.ts +203 -0
  52. package/tests/contractGateSnapshotStore.test.ts +223 -0
  53. package/tests/env.test.ts +73 -0
  54. package/tests/helpers.ts +19 -0
  55. package/tests/mfCache.test.ts +150 -0
  56. package/tests/moduleFederationCss.test.ts +392 -0
  57. package/tests/registration.test.ts +112 -0
  58. package/tests/telemetry.test.ts +360 -0
  59. package/tests/telemetryAutopilot.test.ts +993 -0
  60. package/tests/telemetryCanaryOrchestrator.test.ts +140 -0
  61. package/tests/telemetryLifecycle.test.ts +168 -0
  62. package/tests/telemetryTraceparent.test.ts +167 -0
  63. package/tests/tsconfig.json +11 -0
  64. package/tsconfig.json +10 -0
@@ -0,0 +1,819 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
+ })();
18
+ (()=>{
19
+ __webpack_require__.r = (exports1)=>{
20
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
+ value: 'Module'
22
+ });
23
+ Object.defineProperty(exports1, '__esModule', {
24
+ value: true
25
+ });
26
+ };
27
+ })();
28
+ var __webpack_exports__ = {};
29
+ __webpack_require__.r(__webpack_exports__);
30
+ class TelemetryStartupHealthError extends Error {
31
+ constructor(failedExporters){
32
+ super(`Telemetry startup health check failed for exporters: ${failedExporters.map((item)=>item.name).join(', ')}`), this.code = 'TELEMETRY_EXPORTER_STARTUP_HEALTH_FAILED';
33
+ this.name = 'TelemetryStartupHealthError';
34
+ this.failedExporters = failedExporters;
35
+ }
36
+ }
37
+ const DEFAULT_OTLP_ENDPOINT = 'http://127.0.0.1:4318/v1/logs';
38
+ const DEFAULT_VM_ENDPOINT = 'http://127.0.0.1:8428/api/v1/import/prometheus';
39
+ const DEFAULT_TIMEOUT_MS = 5000;
40
+ function clamp(value, min, max) {
41
+ return Math.max(min, Math.min(max, value));
42
+ }
43
+ function isRecord(value) {
44
+ return 'object' == typeof value && null !== value && !Array.isArray(value);
45
+ }
46
+ function redactObject(value, redactionKeys) {
47
+ if (!isRecord(value)) return;
48
+ const output = {};
49
+ for (const [key, nested] of Object.entries(value)){
50
+ if (redactionKeys.has(key)) {
51
+ output[key] = '[REDACTED]';
52
+ continue;
53
+ }
54
+ if (Array.isArray(nested)) {
55
+ output[key] = nested.map((item)=>{
56
+ if (isRecord(item)) return redactObject(item, redactionKeys);
57
+ return item;
58
+ });
59
+ continue;
60
+ }
61
+ if (isRecord(nested)) {
62
+ output[key] = redactObject(nested, redactionKeys);
63
+ continue;
64
+ }
65
+ output[key] = nested;
66
+ }
67
+ return output;
68
+ }
69
+ function normalizeLabels(labels) {
70
+ if (!labels) return;
71
+ const normalized = {};
72
+ for (const [key, value] of Object.entries(labels))if (null != value) normalized[key] = String(value);
73
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
74
+ }
75
+ function extractError(args) {
76
+ for (const arg of args)if (arg instanceof Error) return {
77
+ name: arg.name,
78
+ message: arg.message,
79
+ stack: arg.stack
80
+ };
81
+ }
82
+ function toTelemetryEnvelope(event, input) {
83
+ const base = {
84
+ timestamp: Date.now(),
85
+ service: input.service,
86
+ module: input.module,
87
+ environment: input.environment,
88
+ ...input.traceId ? {
89
+ traceId: input.traceId
90
+ } : {},
91
+ ...input.spanId ? {
92
+ spanId: input.spanId
93
+ } : {},
94
+ ...input.attributes ? {
95
+ attributes: input.attributes
96
+ } : {}
97
+ };
98
+ if ('log' === event.type) {
99
+ const payload = event.payload;
100
+ const args = payload.args || [];
101
+ const signalType = 'trace' === payload.level ? 'trace' : 'log';
102
+ return {
103
+ ...base,
104
+ signalType,
105
+ name: payload.message,
106
+ level: payload.level,
107
+ attributes: {
108
+ ...base.attributes || {},
109
+ args
110
+ },
111
+ error: extractError(args)
112
+ };
113
+ }
114
+ if ('timing' === event.type) return {
115
+ ...base,
116
+ signalType: 'metric',
117
+ name: event.payload.name,
118
+ value: event.payload.dur,
119
+ unit: 'ms',
120
+ tags: normalizeLabels(event.payload.tags),
121
+ attributes: {
122
+ ...base.attributes || {},
123
+ desc: event.payload.desc,
124
+ args: event.payload.args
125
+ }
126
+ };
127
+ return {
128
+ ...base,
129
+ signalType: 'metric',
130
+ name: event.payload.name,
131
+ value: 1,
132
+ unit: 'count',
133
+ tags: normalizeLabels(event.payload.tags),
134
+ attributes: {
135
+ ...base.attributes || {},
136
+ args: event.payload.args
137
+ }
138
+ };
139
+ }
140
+ async function postWithTimeout(options) {
141
+ const controller = new AbortController();
142
+ const timer = setTimeout(()=>controller.abort(), options.timeoutMs);
143
+ if ('function' == typeof timer.unref) timer.unref();
144
+ try {
145
+ const response = await fetch(options.endpoint, {
146
+ method: 'POST',
147
+ body: options.body,
148
+ headers: options.headers,
149
+ signal: controller.signal
150
+ });
151
+ if (!response.ok) throw new Error(`Telemetry exporter request failed: ${response.status} ${response.statusText}`);
152
+ } finally{
153
+ clearTimeout(timer);
154
+ }
155
+ }
156
+ function sanitizeMetricName(value) {
157
+ return value.replace(/[^a-zA-Z0-9_:]/g, '_').replace(/_+/g, '_');
158
+ }
159
+ function escapeLabelValue(value) {
160
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
161
+ }
162
+ function toPrometheusLine(envelope, metricPrefix) {
163
+ const metricName = sanitizeMetricName(`${metricPrefix}_${envelope.signalType}_${envelope.name}`);
164
+ const labels = {
165
+ service: envelope.service,
166
+ module: envelope.module,
167
+ environment: envelope.environment,
168
+ ...envelope.level ? {
169
+ level: envelope.level
170
+ } : {},
171
+ ...envelope.traceId ? {
172
+ trace_id: envelope.traceId
173
+ } : {},
174
+ ...envelope.spanId ? {
175
+ span_id: envelope.spanId
176
+ } : {},
177
+ ...envelope.tags || {}
178
+ };
179
+ const labelPairs = Object.entries(labels).sort(([a], [b])=>a.localeCompare(b)).map(([key, value])=>`${sanitizeMetricName(key)}="${escapeLabelValue(value)}"`);
180
+ const labelText = labelPairs.length > 0 ? `{${labelPairs.join(',')}}` : '';
181
+ const value = 'number' == typeof envelope.value && Number.isFinite(envelope.value) ? envelope.value : 1;
182
+ const timestampMs = envelope.timestamp;
183
+ return `${metricName}${labelText} ${value} ${timestampMs}`;
184
+ }
185
+ function createOtlpTelemetryExporter(options = {}) {
186
+ const endpoint = options.endpoint || DEFAULT_OTLP_ENDPOINT;
187
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
188
+ const headers = {
189
+ 'content-type': 'application/json',
190
+ ...options.headers || {}
191
+ };
192
+ return {
193
+ name: 'otlp',
194
+ async emit (batch) {
195
+ if (0 === batch.length) return;
196
+ const body = JSON.stringify({
197
+ resource: {
198
+ service: batch[0]?.service,
199
+ module: batch[0]?.module,
200
+ environment: batch[0]?.environment
201
+ },
202
+ emittedAt: Date.now(),
203
+ events: batch
204
+ });
205
+ await postWithTimeout({
206
+ endpoint,
207
+ body,
208
+ headers,
209
+ timeoutMs
210
+ });
211
+ }
212
+ };
213
+ }
214
+ function createVictoriaMetricsTelemetryExporter(options = {}) {
215
+ const endpoint = options.endpoint || DEFAULT_VM_ENDPOINT;
216
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
217
+ const metricPrefix = sanitizeMetricName(options.metricPrefix || 'modernjs');
218
+ const headers = {
219
+ 'content-type': 'text/plain; version=0.0.4',
220
+ ...options.headers || {}
221
+ };
222
+ return {
223
+ name: 'victoria-metrics',
224
+ async emit (batch) {
225
+ if (0 === batch.length) return;
226
+ const lines = batch.map((item)=>toPrometheusLine(item, metricPrefix));
227
+ await postWithTimeout({
228
+ endpoint,
229
+ body: `${lines.join('\n')}\n`,
230
+ headers,
231
+ timeoutMs
232
+ });
233
+ }
234
+ };
235
+ }
236
+ class TelemetryRegistry {
237
+ async register(exporter) {
238
+ this.exporters.push(exporter);
239
+ this.exporterHealth.set(exporter.name, {
240
+ name: exporter.name,
241
+ healthy: true,
242
+ failures: 0
243
+ });
244
+ if (exporter.init) try {
245
+ await exporter.init({
246
+ service: this.service,
247
+ module: this.module,
248
+ environment: this.environment
249
+ });
250
+ this.markExporterHealthy(exporter.name);
251
+ } catch (error) {
252
+ this.markExporterFailure(exporter.name, error);
253
+ throw error;
254
+ }
255
+ else this.markExporterHealthy(exporter.name);
256
+ }
257
+ getOrCreateExporterHealth(name) {
258
+ const existing = this.exporterHealth.get(name);
259
+ if (existing) return existing;
260
+ const next = {
261
+ name,
262
+ healthy: true,
263
+ failures: 0
264
+ };
265
+ this.exporterHealth.set(name, next);
266
+ return next;
267
+ }
268
+ markExporterHealthy(name) {
269
+ const status = this.getOrCreateExporterHealth(name);
270
+ status.healthy = true;
271
+ status.lastSuccessAt = Date.now();
272
+ status.lastError = void 0;
273
+ }
274
+ markExporterFailure(name, error) {
275
+ const status = this.getOrCreateExporterHealth(name);
276
+ status.healthy = false;
277
+ status.failures += 1;
278
+ status.lastFailureAt = Date.now();
279
+ status.lastError = error instanceof Error ? error.message : String(error);
280
+ }
281
+ maybeEmitSloAlert(type, value, threshold) {
282
+ if (!this.onSloAlert || value < threshold) return;
283
+ const now = Date.now();
284
+ const lastTimestamp = this.lastSloAlertAt.get(type) ?? 0;
285
+ if (now - lastTimestamp < this.alertCooldownMs) return;
286
+ this.lastSloAlertAt.set(type, now);
287
+ const queueDepth = this.queue.length;
288
+ try {
289
+ this.onSloAlert({
290
+ timestamp: now,
291
+ service: this.service,
292
+ module: this.module,
293
+ environment: this.environment,
294
+ type,
295
+ value,
296
+ threshold,
297
+ queueDepth,
298
+ queueCapacity: this.maxQueueSize,
299
+ queueUtilization: queueDepth / this.maxQueueSize,
300
+ totalDropped: this.totalDroppedCount
301
+ });
302
+ } catch (_error) {}
303
+ }
304
+ enqueue(envelope) {
305
+ if (this.samplingRate < 1 && Math.random() > this.samplingRate) return;
306
+ const redactedEnvelope = this.redactionKeys.size > 0 ? {
307
+ ...envelope,
308
+ attributes: redactObject(envelope.attributes, this.redactionKeys)
309
+ } : envelope;
310
+ if (this.queue.length >= this.maxQueueSize) {
311
+ this.queue.shift();
312
+ this.droppedCount += 1;
313
+ this.totalDroppedCount += 1;
314
+ this.maybeEmitSloAlert('queue.drop', this.totalDroppedCount, this.queueDroppedWarnThreshold);
315
+ }
316
+ this.queue.push(redactedEnvelope);
317
+ this.maybeEmitSloAlert('queue.utilization', this.queue.length / this.maxQueueSize, this.queueUtilizationWarnThreshold);
318
+ if (this.queue.length >= this.maxBatchSize) this.flush();
319
+ }
320
+ enqueueMetric(input) {
321
+ this.enqueue({
322
+ timestamp: Date.now(),
323
+ service: this.service,
324
+ module: this.module,
325
+ environment: this.environment,
326
+ signalType: 'metric',
327
+ name: input.name,
328
+ value: input.value,
329
+ unit: input.unit || 'count',
330
+ traceId: input.traceId,
331
+ spanId: input.spanId,
332
+ parentSpanId: input.parentSpanId,
333
+ tags: input.tags,
334
+ attributes: input.attributes
335
+ });
336
+ }
337
+ enqueueLog(input) {
338
+ this.enqueue({
339
+ timestamp: Date.now(),
340
+ service: this.service,
341
+ module: this.module,
342
+ environment: this.environment,
343
+ signalType: 'log',
344
+ name: input.name,
345
+ level: input.level,
346
+ traceId: input.traceId,
347
+ spanId: input.spanId,
348
+ parentSpanId: input.parentSpanId,
349
+ tags: input.tags,
350
+ attributes: input.attributes,
351
+ error: input.error
352
+ });
353
+ }
354
+ enqueueTrace(input) {
355
+ this.enqueue({
356
+ timestamp: Date.now(),
357
+ service: this.service,
358
+ module: this.module,
359
+ environment: this.environment,
360
+ signalType: 'trace',
361
+ name: input.name,
362
+ traceId: input.traceId,
363
+ spanId: input.spanId,
364
+ parentSpanId: input.parentSpanId,
365
+ tags: input.tags,
366
+ attributes: input.attributes
367
+ });
368
+ }
369
+ buildDroppedEnvelope(droppedCount) {
370
+ return {
371
+ timestamp: Date.now(),
372
+ service: this.service,
373
+ module: this.module,
374
+ environment: this.environment,
375
+ signalType: 'metric',
376
+ name: 'telemetry.queue.dropped',
377
+ value: droppedCount,
378
+ unit: 'count',
379
+ tags: {
380
+ reason: 'queue_backpressure'
381
+ }
382
+ };
383
+ }
384
+ buildQueueDepthEnvelope(queueDepth) {
385
+ return {
386
+ timestamp: Date.now(),
387
+ service: this.service,
388
+ module: this.module,
389
+ environment: this.environment,
390
+ signalType: 'metric',
391
+ name: 'telemetry.queue.depth',
392
+ value: queueDepth,
393
+ unit: 'count',
394
+ tags: {
395
+ capacity: String(this.maxQueueSize)
396
+ }
397
+ };
398
+ }
399
+ buildQueueUtilizationEnvelope(queueDepth) {
400
+ return {
401
+ timestamp: Date.now(),
402
+ service: this.service,
403
+ module: this.module,
404
+ environment: this.environment,
405
+ signalType: 'metric',
406
+ name: 'telemetry.queue.utilization',
407
+ value: queueDepth / this.maxQueueSize,
408
+ unit: 'ratio',
409
+ tags: {
410
+ capacity: String(this.maxQueueSize)
411
+ }
412
+ };
413
+ }
414
+ async emitBatch(batch) {
415
+ const results = await Promise.allSettled(this.exporters.map(async (exporter)=>{
416
+ await exporter.emit(batch);
417
+ return exporter.name;
418
+ }));
419
+ for (const [index, result] of results.entries()){
420
+ const exporterName = this.exporters[index]?.name || `exporter-${index}`;
421
+ if ('rejected' === result.status) {
422
+ this.markExporterFailure(exporterName, result.reason);
423
+ continue;
424
+ }
425
+ this.markExporterHealthy(exporterName);
426
+ }
427
+ }
428
+ buildStartupProbeEnvelope() {
429
+ return {
430
+ timestamp: Date.now(),
431
+ service: this.service,
432
+ module: this.module,
433
+ environment: this.environment,
434
+ signalType: 'log',
435
+ name: 'telemetry.exporter.startup_probe',
436
+ level: 'info',
437
+ tags: {
438
+ phase: 'startup'
439
+ },
440
+ attributes: {
441
+ source: 'TelemetryRegistry'
442
+ }
443
+ };
444
+ }
445
+ async startupHealthCheck(options) {
446
+ if (0 === this.exporters.length) return;
447
+ const probeBatch = [
448
+ this.buildStartupProbeEnvelope()
449
+ ];
450
+ const failedExporters = [];
451
+ await Promise.all(this.exporters.map(async (exporter)=>{
452
+ try {
453
+ await exporter.emit(probeBatch);
454
+ this.markExporterHealthy(exporter.name);
455
+ } catch (error) {
456
+ this.markExporterFailure(exporter.name, error);
457
+ const status = this.exporterHealth.get(exporter.name);
458
+ if (status) failedExporters.push({
459
+ ...status
460
+ });
461
+ }
462
+ }));
463
+ if ((options?.failLoud ?? true) && failedExporters.length > 0) throw new TelemetryStartupHealthError(failedExporters);
464
+ }
465
+ getExporterHealth() {
466
+ return Array.from(this.exporterHealth.values()).map((item)=>({
467
+ ...item
468
+ }));
469
+ }
470
+ getQueueStats() {
471
+ return {
472
+ depth: this.queue.length,
473
+ capacity: this.maxQueueSize,
474
+ utilization: this.queue.length / this.maxQueueSize,
475
+ pendingDropped: this.droppedCount,
476
+ totalDropped: this.totalDroppedCount
477
+ };
478
+ }
479
+ async flushInternal() {
480
+ const queueDepthBeforeFlush = this.queue.length;
481
+ if (queueDepthBeforeFlush > 0) {
482
+ this.queue.unshift(this.buildQueueUtilizationEnvelope(queueDepthBeforeFlush));
483
+ this.queue.unshift(this.buildQueueDepthEnvelope(queueDepthBeforeFlush));
484
+ }
485
+ if (this.droppedCount > 0) {
486
+ const droppedCount = this.droppedCount;
487
+ this.droppedCount = 0;
488
+ this.queue.unshift(this.buildDroppedEnvelope(droppedCount));
489
+ }
490
+ if (0 === this.queue.length) return;
491
+ if (0 === this.exporters.length) {
492
+ this.queue.length = 0;
493
+ return;
494
+ }
495
+ while(this.queue.length > 0){
496
+ const batch = this.queue.splice(0, this.maxBatchSize);
497
+ await this.emitBatch(batch);
498
+ }
499
+ await Promise.allSettled(this.exporters.map(async (exporter)=>{
500
+ if (exporter.flush) await exporter.flush();
501
+ }));
502
+ }
503
+ flush() {
504
+ if (this.flushing) return this.flushing;
505
+ this.flushing = this.flushInternal().finally(()=>{
506
+ this.flushing = null;
507
+ });
508
+ return this.flushing;
509
+ }
510
+ async shutdown() {
511
+ if (this.flushTimer) clearInterval(this.flushTimer);
512
+ await this.flush();
513
+ await Promise.allSettled(this.exporters.map(async (exporter)=>{
514
+ if (exporter.shutdown) await exporter.shutdown();
515
+ }));
516
+ }
517
+ constructor(options){
518
+ this.exporters = [];
519
+ this.queue = [];
520
+ this.droppedCount = 0;
521
+ this.totalDroppedCount = 0;
522
+ this.flushing = null;
523
+ this.exporterHealth = new Map();
524
+ this.lastSloAlertAt = new Map();
525
+ this.service = options.service;
526
+ this.module = options.module;
527
+ this.environment = options.environment;
528
+ this.samplingRate = clamp(options.samplingRate ?? 1, 0, 1);
529
+ this.maxBatchSize = Math.max(1, options.maxBatchSize ?? 50);
530
+ this.maxQueueSize = Math.max(1, options.maxQueueSize ?? 1000);
531
+ this.flushIntervalMs = Math.max(50, options.flushIntervalMs ?? 1000);
532
+ this.redactionKeys = new Set(options.redactionKeys || []);
533
+ this.queueUtilizationWarnThreshold = clamp(options.slo?.queueUtilizationWarnThreshold ?? 0.8, 0, 1);
534
+ this.queueDroppedWarnThreshold = Math.max(1, options.slo?.queueDroppedWarnThreshold ?? 1);
535
+ this.alertCooldownMs = Math.max(0, options.slo?.alertCooldownMs ?? 60000);
536
+ this.onSloAlert = options.slo?.onAlert;
537
+ this.flushTimer = setInterval(()=>{
538
+ this.flush();
539
+ }, this.flushIntervalMs);
540
+ if ('function' == typeof this.flushTimer.unref) this.flushTimer.unref();
541
+ }
542
+ }
543
+ class TelemetryCanaryOrchestrator {
544
+ setRequiredContractGates(gates) {
545
+ this.requiredContractGates = Array.from(new Set(gates.map((item)=>item.trim()).filter(Boolean)));
546
+ }
547
+ addRequiredContractGate(name) {
548
+ const normalizedName = name.trim();
549
+ if (!normalizedName) return;
550
+ if (!this.requiredContractGates.includes(normalizedName)) this.requiredContractGates.push(normalizedName);
551
+ }
552
+ setContractGate(name, passed, reason) {
553
+ this.contractGates.set(name, {
554
+ name,
555
+ passed,
556
+ reason,
557
+ updatedAt: Date.now()
558
+ });
559
+ }
560
+ setContractGates(gates) {
561
+ for (const [name, value] of Object.entries(gates)){
562
+ if ('boolean' == typeof value) {
563
+ this.setContractGate(name, value);
564
+ continue;
565
+ }
566
+ this.setContractGate(name, value.passed, value.reason);
567
+ }
568
+ }
569
+ resetToCanary() {
570
+ this.state = 'canary';
571
+ this.consecutiveHealthy = 0;
572
+ this.consecutiveFailures = 0;
573
+ }
574
+ collectFailures() {
575
+ const failures = [];
576
+ const queueStats = this.registry.getQueueStats();
577
+ const unhealthyExporterCount = this.registry.getExporterHealth().filter((item)=>!item.healthy).length;
578
+ if (queueStats.utilization > this.maxQueueUtilization) failures.push({
579
+ reason: 'queue_utilization',
580
+ threshold: this.maxQueueUtilization,
581
+ value: queueStats.utilization
582
+ });
583
+ if (queueStats.totalDropped > this.maxTotalDropped) failures.push({
584
+ reason: 'queue_dropped',
585
+ threshold: this.maxTotalDropped,
586
+ value: queueStats.totalDropped
587
+ });
588
+ if (unhealthyExporterCount > this.maxUnhealthyExporters) failures.push({
589
+ reason: 'unhealthy_exporter',
590
+ threshold: this.maxUnhealthyExporters,
591
+ value: unhealthyExporterCount
592
+ });
593
+ for (const gateName of this.requiredContractGates){
594
+ const gate = this.contractGates.get(gateName);
595
+ if (!gate) {
596
+ failures.push({
597
+ reason: 'contract_gate_missing',
598
+ gate: gateName,
599
+ message: `Contract gate "${gateName}" is missing`
600
+ });
601
+ continue;
602
+ }
603
+ if (!gate.passed) failures.push({
604
+ reason: 'contract_gate_failed',
605
+ gate: gateName,
606
+ message: gate.reason || `Contract gate "${gateName}" is not passing`
607
+ });
608
+ }
609
+ return {
610
+ failures,
611
+ queueStats,
612
+ unhealthyExporterCount
613
+ };
614
+ }
615
+ evaluate() {
616
+ const now = Date.now();
617
+ const { failures, queueStats, unhealthyExporterCount } = this.collectFailures();
618
+ let action = 'hold';
619
+ if (failures.length > 0) {
620
+ this.consecutiveHealthy = 0;
621
+ this.consecutiveFailures += 1;
622
+ if ('rolled_back' !== this.state && this.consecutiveFailures >= this.rollbackConsecutiveFailures) {
623
+ this.state = 'rolled_back';
624
+ action = 'rollback';
625
+ }
626
+ } else {
627
+ this.consecutiveFailures = 0;
628
+ this.consecutiveHealthy += 1;
629
+ if ('canary' === this.state && this.consecutiveHealthy >= this.minConsecutiveHealthyEvaluations) {
630
+ this.state = 'promoted';
631
+ action = 'promote';
632
+ }
633
+ }
634
+ const decision = {
635
+ timestamp: now,
636
+ action,
637
+ state: this.state,
638
+ consecutiveHealthy: this.consecutiveHealthy,
639
+ consecutiveFailures: this.consecutiveFailures,
640
+ failures,
641
+ queueStats,
642
+ unhealthyExporterCount,
643
+ contractGates: Array.from(this.contractGates.values()).map((item)=>({
644
+ ...item
645
+ }))
646
+ };
647
+ try {
648
+ this.onEvaluate?.(decision);
649
+ } catch (_error) {}
650
+ if ('promote' === action) try {
651
+ this.onPromote?.(decision);
652
+ } catch (_error) {}
653
+ if ('rollback' === action) try {
654
+ this.onRollback?.(decision);
655
+ } catch (_error) {}
656
+ return decision;
657
+ }
658
+ getStatusSnapshot() {
659
+ const now = Date.now();
660
+ const { failures, queueStats, unhealthyExporterCount } = this.collectFailures();
661
+ return {
662
+ timestamp: now,
663
+ state: this.state,
664
+ consecutiveHealthy: this.consecutiveHealthy,
665
+ consecutiveFailures: this.consecutiveFailures,
666
+ queueStats,
667
+ unhealthyExporterCount,
668
+ requiredContractGates: [
669
+ ...this.requiredContractGates
670
+ ],
671
+ contractGates: Array.from(this.contractGates.values()).map((item)=>({
672
+ ...item
673
+ })),
674
+ failurePreview: failures
675
+ };
676
+ }
677
+ start() {
678
+ if (this.evaluationTimer) return;
679
+ this.evaluationTimer = setInterval(()=>{
680
+ this.evaluate();
681
+ }, this.evaluationIntervalMs);
682
+ if ('function' == typeof this.evaluationTimer.unref) this.evaluationTimer.unref();
683
+ }
684
+ stop() {
685
+ if (this.evaluationTimer) {
686
+ clearInterval(this.evaluationTimer);
687
+ this.evaluationTimer = void 0;
688
+ }
689
+ }
690
+ constructor(options){
691
+ this.requiredContractGates = [];
692
+ this.contractGates = new Map();
693
+ this.state = 'canary';
694
+ this.consecutiveHealthy = 0;
695
+ this.consecutiveFailures = 0;
696
+ this.registry = options.registry;
697
+ this.evaluationIntervalMs = Math.max(250, options.evaluationIntervalMs ?? 15000);
698
+ this.minConsecutiveHealthyEvaluations = Math.max(1, options.minConsecutiveHealthyEvaluations ?? 3);
699
+ this.rollbackConsecutiveFailures = Math.max(1, options.rollbackConsecutiveFailures ?? 2);
700
+ this.maxQueueUtilization = clamp(options.maxQueueUtilization ?? 0.8, 0, 1);
701
+ this.maxTotalDropped = Math.max(0, options.maxTotalDropped ?? 0);
702
+ this.maxUnhealthyExporters = Math.max(0, options.maxUnhealthyExporters ?? 0);
703
+ this.setRequiredContractGates(options.requiredContractGates || []);
704
+ this.onEvaluate = options.onEvaluate;
705
+ this.onPromote = options.onPromote;
706
+ this.onRollback = options.onRollback;
707
+ }
708
+ }
709
+ function normalizeMetricsInput(prefixOrTags, tags) {
710
+ if ('string' == typeof prefixOrTags) return {
711
+ prefix: prefixOrTags,
712
+ tags: tags || {}
713
+ };
714
+ if (isRecord(prefixOrTags)) return {
715
+ prefix: void 0,
716
+ tags: prefixOrTags
717
+ };
718
+ return {
719
+ prefix: void 0,
720
+ tags: tags || {}
721
+ };
722
+ }
723
+ function normalizeMetricName(name, prefix) {
724
+ return prefix && prefix.length > 0 ? `${prefix}.${name}` : name;
725
+ }
726
+ function toTelemetryMetricTags(tags) {
727
+ const output = {};
728
+ for (const [key, value] of Object.entries(tags))if (null != value) output[key] = String(value);
729
+ return output;
730
+ }
731
+ function getTraceContext(tags) {
732
+ const traceId = 'string' == typeof tags.trace_id ? tags.trace_id : 'string' == typeof tags.traceId ? tags.traceId : void 0;
733
+ const spanId = 'string' == typeof tags.span_id ? tags.span_id : 'string' == typeof tags.spanId ? tags.spanId : void 0;
734
+ const parentSpanId = 'string' == typeof tags.parent_span_id ? tags.parent_span_id : 'string' == typeof tags.parentSpanId ? tags.parentSpanId : void 0;
735
+ return {
736
+ traceId,
737
+ spanId,
738
+ parentSpanId
739
+ };
740
+ }
741
+ const createTelemetryAwareMetrics = (baseMetrics, registry)=>{
742
+ const emitCounter = (name, value, prefixOrTags, tags)=>{
743
+ const normalized = normalizeMetricsInput(prefixOrTags, tags);
744
+ baseMetrics.emitCounter(name, value, normalized.prefix, normalized.tags);
745
+ try {
746
+ const metricName = normalizeMetricName(name, normalized.prefix);
747
+ const traceContext = getTraceContext(normalized.tags);
748
+ registry.enqueueMetric({
749
+ name: metricName,
750
+ value,
751
+ unit: 'count',
752
+ traceId: traceContext.traceId,
753
+ spanId: traceContext.spanId,
754
+ parentSpanId: traceContext.parentSpanId,
755
+ tags: toTelemetryMetricTags(normalized.tags),
756
+ attributes: normalized.tags
757
+ });
758
+ } catch (_error) {}
759
+ };
760
+ const emitTimer = (name, value, prefixOrTags, tags)=>{
761
+ const normalized = normalizeMetricsInput(prefixOrTags, tags);
762
+ baseMetrics.emitTimer(name, value, normalized.prefix, normalized.tags);
763
+ try {
764
+ const metricName = normalizeMetricName(name, normalized.prefix);
765
+ const traceContext = getTraceContext(normalized.tags);
766
+ registry.enqueueMetric({
767
+ name: metricName,
768
+ value,
769
+ unit: 'ms',
770
+ traceId: traceContext.traceId,
771
+ spanId: traceContext.spanId,
772
+ parentSpanId: traceContext.parentSpanId,
773
+ tags: toTelemetryMetricTags(normalized.tags),
774
+ attributes: normalized.tags
775
+ });
776
+ } catch (_error) {}
777
+ };
778
+ return {
779
+ ...baseMetrics,
780
+ emitCounter,
781
+ emitTimer
782
+ };
783
+ };
784
+ function maybeWarnLegacyOtlpEndpoint(endpoint) {
785
+ if (!endpoint || !endpoint.includes('/v1/metrics')) return;
786
+ console.warn(`[telemetry] OTLP endpoint "${endpoint}" looks like a metrics path. UltraModern telemetry exporter expects log-style envelopes (default: ${DEFAULT_OTLP_ENDPOINT}).`);
787
+ }
788
+ __webpack_require__.d(__webpack_exports__, {
789
+ TelemetryCanaryOrchestrator: ()=>TelemetryCanaryOrchestrator,
790
+ TelemetryRegistry: ()=>TelemetryRegistry,
791
+ TelemetryStartupHealthError: ()=>TelemetryStartupHealthError,
792
+ createOtlpTelemetryExporter: ()=>createOtlpTelemetryExporter,
793
+ createVictoriaMetricsTelemetryExporter: ()=>createVictoriaMetricsTelemetryExporter,
794
+ maybeWarnLegacyOtlpEndpoint: ()=>maybeWarnLegacyOtlpEndpoint,
795
+ toTelemetryEnvelope: ()=>toTelemetryEnvelope
796
+ }, {
797
+ createTelemetryAwareMetrics: createTelemetryAwareMetrics
798
+ });
799
+ exports.TelemetryCanaryOrchestrator = __webpack_exports__.TelemetryCanaryOrchestrator;
800
+ exports.TelemetryRegistry = __webpack_exports__.TelemetryRegistry;
801
+ exports.TelemetryStartupHealthError = __webpack_exports__.TelemetryStartupHealthError;
802
+ exports.createOtlpTelemetryExporter = __webpack_exports__.createOtlpTelemetryExporter;
803
+ exports.createTelemetryAwareMetrics = __webpack_exports__.createTelemetryAwareMetrics;
804
+ exports.createVictoriaMetricsTelemetryExporter = __webpack_exports__.createVictoriaMetricsTelemetryExporter;
805
+ exports.maybeWarnLegacyOtlpEndpoint = __webpack_exports__.maybeWarnLegacyOtlpEndpoint;
806
+ exports.toTelemetryEnvelope = __webpack_exports__.toTelemetryEnvelope;
807
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
808
+ "TelemetryCanaryOrchestrator",
809
+ "TelemetryRegistry",
810
+ "TelemetryStartupHealthError",
811
+ "createOtlpTelemetryExporter",
812
+ "createTelemetryAwareMetrics",
813
+ "createVictoriaMetricsTelemetryExporter",
814
+ "maybeWarnLegacyOtlpEndpoint",
815
+ "toTelemetryEnvelope"
816
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
817
+ Object.defineProperty(exports, '__esModule', {
818
+ value: true
819
+ });