@ooopsstudio/performance 0.9.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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/budget-engine-KDA4FRYU.js +160 -0
  4. package/dist/budget-engine-KDA4FRYU.js.map +1 -0
  5. package/dist/chunk-2XXIOHOO.js +58 -0
  6. package/dist/chunk-2XXIOHOO.js.map +1 -0
  7. package/dist/chunk-3GMKPRQP.js +197 -0
  8. package/dist/chunk-3GMKPRQP.js.map +1 -0
  9. package/dist/chunk-J6KBSLYO.js +118 -0
  10. package/dist/chunk-J6KBSLYO.js.map +1 -0
  11. package/dist/chunk-MMO5UEUU.js +20 -0
  12. package/dist/chunk-MMO5UEUU.js.map +1 -0
  13. package/dist/chunk-RL2752FW.js +31 -0
  14. package/dist/chunk-RL2752FW.js.map +1 -0
  15. package/dist/chunk-RY65GERM.js +159 -0
  16. package/dist/chunk-RY65GERM.js.map +1 -0
  17. package/dist/chunk-W72QW2AD.js +29 -0
  18. package/dist/chunk-W72QW2AD.js.map +1 -0
  19. package/dist/chunk-ZAXGEDZW.js +1184 -0
  20. package/dist/chunk-ZAXGEDZW.js.map +1 -0
  21. package/dist/custom/exporters/http.d.ts +11 -0
  22. package/dist/custom/exporters/http.js +364 -0
  23. package/dist/custom/exporters/http.js.map +1 -0
  24. package/dist/custom/exporters/raw.d.ts +8 -0
  25. package/dist/custom/exporters/raw.js +6 -0
  26. package/dist/custom/exporters/raw.js.map +1 -0
  27. package/dist/custom.d.ts +58 -0
  28. package/dist/custom.js +299 -0
  29. package/dist/custom.js.map +1 -0
  30. package/dist/development.d.ts +19 -0
  31. package/dist/development.js +55 -0
  32. package/dist/development.js.map +1 -0
  33. package/dist/event-export-manager-DEB64LWZ.js +632 -0
  34. package/dist/event-export-manager-DEB64LWZ.js.map +1 -0
  35. package/dist/index.d.ts +26 -0
  36. package/dist/index.js +205 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/monitors-VNNUIKAK.js +753 -0
  39. package/dist/monitors-VNNUIKAK.js.map +1 -0
  40. package/dist/n1-detector-6IQ2DINP.js +247 -0
  41. package/dist/n1-detector-6IQ2DINP.js.map +1 -0
  42. package/dist/observability.d.ts +32 -0
  43. package/dist/observability.js +76 -0
  44. package/dist/observability.js.map +1 -0
  45. package/dist/ports-DwUYdKj-.d.ts +23 -0
  46. package/dist/production.d.ts +19 -0
  47. package/dist/production.js +42 -0
  48. package/dist/production.js.map +1 -0
  49. package/dist/public/types.d.ts +11 -0
  50. package/dist/public/types.js +3 -0
  51. package/dist/public/types.js.map +1 -0
  52. package/package.json +74 -0
@@ -0,0 +1,632 @@
1
+ import { deepFreezePerformanceValue } from './chunk-J6KBSLYO.js';
2
+ import { MAX_PERFORMANCE_TIMER_MS, withPerformanceExportTimeout, serializePerformanceEventRecord, MAX_PERFORMANCE_EXPORT_BATCH_BYTES, getPerformanceExportErrorMetadata, sleep, createPerformanceExportError, MAX_PERFORMANCE_EXPORT_BATCH_COUNT } from './chunk-3GMKPRQP.js';
3
+ import { isRuntimeProxy, ignoreRuntimePromiseRejection, isRuntimePromise } from './chunk-RL2752FW.js';
4
+ import { normalizeError } from '@ooopsstudio/core/utils/error/normalize-error';
5
+
6
+ // src/performance/core/event-export-lifecycle.ts
7
+ function createEventExporterLifecycleOperations() {
8
+ const completed = /* @__PURE__ */ new Set();
9
+ const pending = /* @__PURE__ */ new Map();
10
+ return {
11
+ run(name, hookName, hook) {
12
+ const key = `${hookName}:${name}`;
13
+ if (completed.has(key)) return Promise.resolve();
14
+ const existing = pending.get(key);
15
+ if (existing) return existing;
16
+ const operation = Promise.resolve().then(async () => await hook());
17
+ pending.set(key, operation);
18
+ void operation.then(
19
+ () => {
20
+ completed.add(key);
21
+ pending.delete(key);
22
+ },
23
+ () => pending.delete(key)
24
+ );
25
+ return operation;
26
+ }
27
+ };
28
+ }
29
+
30
+ // src/performance/core/event-export-manager.ts
31
+ var MAX_EXPORT_OPERATION_TIMEOUT_MS = 5e3;
32
+ var MAX_EXPORT_RETRY_DELAY_MS = 5e3;
33
+ var MAX_EXPORT_DELIVERY_BUDGET_MS = 3e4;
34
+ var KNOWN_EXPORT_FAILURE_CODE = /^(?:performance_export_timeout|http_(?:rate_limited|server_error|client_error|unexpected_status)|fetch_(?:aborted|failed)|invalid_fetch_response|event_serialization_failed)$/;
35
+ var DROPPED_TOTAL_METRIC = "_performance_dropped_total";
36
+ var QUEUE_SIZE_METRIC = "_performance_export_queue_size";
37
+ var captureExporterMethod = (target, key) => {
38
+ if (isRuntimeProxy(target) || typeof target !== "object" && typeof target !== "function" || target === null) return void 0;
39
+ try {
40
+ let owner = target;
41
+ for (let depth = 0; owner && depth < 16; depth += 1) {
42
+ if (isRuntimeProxy(owner)) return void 0;
43
+ const descriptor = Object.getOwnPropertyDescriptor(owner, key);
44
+ if (descriptor) {
45
+ if (!("value" in descriptor) || typeof descriptor.value !== "function") return void 0;
46
+ const method = descriptor.value;
47
+ return (...args) => {
48
+ const result = Reflect.apply(method, target, args);
49
+ if (result !== void 0 && !isRuntimePromise(result)) throw createPerformanceExportError("", {
50
+ retryable: false,
51
+ code: "invalid_exporter_result"
52
+ });
53
+ return result;
54
+ };
55
+ }
56
+ owner = Object.getPrototypeOf(owner);
57
+ }
58
+ } catch {
59
+ return void 0;
60
+ }
61
+ return void 0;
62
+ };
63
+ var hasExporterProperty = (target, key) => {
64
+ if (isRuntimeProxy(target) || typeof target !== "object" && typeof target !== "function" || target === null) return false;
65
+ try {
66
+ let owner = target;
67
+ for (let depth = 0; owner && depth < 16; depth += 1) {
68
+ if (isRuntimeProxy(owner)) return true;
69
+ if (Object.getOwnPropertyDescriptor(owner, key)) return true;
70
+ owner = Object.getPrototypeOf(owner);
71
+ }
72
+ } catch {
73
+ return true;
74
+ }
75
+ return false;
76
+ };
77
+ var captureErrorsReport = (errors) => {
78
+ if (!errors || typeof errors !== "object" && typeof errors !== "function" || isRuntimeProxy(errors)) return void 0;
79
+ try {
80
+ let owner = errors;
81
+ for (let depth = 0; owner && depth < 16; depth += 1) {
82
+ if (isRuntimeProxy(owner)) return void 0;
83
+ const descriptor = Object.getOwnPropertyDescriptor(owner, "report");
84
+ if (descriptor) {
85
+ if (!("value" in descriptor) || typeof descriptor.value !== "function") return void 0;
86
+ const report = descriptor.value;
87
+ return (...args) => Reflect.apply(report, errors, args);
88
+ }
89
+ owner = Object.getPrototypeOf(owner);
90
+ }
91
+ } catch {
92
+ return void 0;
93
+ }
94
+ return void 0;
95
+ };
96
+ var captureExporter = (name, exporter) => {
97
+ const exportBatch = captureExporterMethod(exporter, "export");
98
+ const flush = captureExporterMethod(exporter, "flush");
99
+ const shutdown = captureExporterMethod(exporter, "shutdown");
100
+ if (!exportBatch) throw new Error(`Performance event exporter "${name}" must provide a data-method export function`);
101
+ if (hasExporterProperty(exporter, "flush") && !flush) {
102
+ throw new Error(`Performance event exporter "${name}" flush must be a data-method function`);
103
+ }
104
+ if (hasExporterProperty(exporter, "shutdown") && !shutdown) {
105
+ throw new Error(`Performance event exporter "${name}" shutdown must be a data-method function`);
106
+ }
107
+ return Object.freeze({
108
+ export: exportBatch,
109
+ ...flush ? { flush } : {},
110
+ ...shutdown ? { shutdown } : {}
111
+ });
112
+ };
113
+ function createEventExportManager(options) {
114
+ if (!options || typeof options !== "object") {
115
+ throw new Error("Performance event export options must be an object");
116
+ }
117
+ const {
118
+ exporters: configuredExporters,
119
+ maxBufferCount,
120
+ maxBufferBytes,
121
+ flushIntervalMs,
122
+ retryAttempts,
123
+ retryBaseDelayMs,
124
+ operationTimeoutMs = 5e3,
125
+ errors,
126
+ observe
127
+ } = options;
128
+ if (isRuntimeProxy(configuredExporters) || !Array.isArray(configuredExporters)) {
129
+ throw new Error("Performance event exporters must be an array");
130
+ }
131
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(configuredExporters, "length");
132
+ const configuredExporterCount = lengthDescriptor && "value" in lengthDescriptor ? lengthDescriptor.value : -1;
133
+ if (!Number.isSafeInteger(configuredExporterCount) || configuredExporterCount > 2) {
134
+ throw new Error("Performance event export supports at most two exporters");
135
+ }
136
+ const configuredEntries = [];
137
+ for (let index = 0; index < configuredExporterCount; index += 1) {
138
+ const descriptor = Object.getOwnPropertyDescriptor(configuredExporters, String(index));
139
+ if (!descriptor || !("value" in descriptor)) {
140
+ throw new Error("Performance event exporters must use data properties");
141
+ }
142
+ const configured = descriptor.value;
143
+ if (!configured || typeof configured !== "object" || isRuntimeProxy(configured)) {
144
+ throw new Error("Performance event exporters must be valid objects");
145
+ }
146
+ let name;
147
+ let exporter;
148
+ try {
149
+ const nameDescriptor = Object.getOwnPropertyDescriptor(configured, "name");
150
+ const exporterDescriptor = Object.getOwnPropertyDescriptor(configured, "exporter");
151
+ if (!nameDescriptor || !("value" in nameDescriptor) || !exporterDescriptor || !("value" in exporterDescriptor)) {
152
+ throw new TypeError();
153
+ }
154
+ name = nameDescriptor.value;
155
+ exporter = exporterDescriptor.value;
156
+ } catch {
157
+ throw new Error("Performance event exporters must use data properties");
158
+ }
159
+ configuredEntries.push({ name, exporter });
160
+ }
161
+ if (!Number.isInteger(maxBufferCount) || maxBufferCount <= 0 || maxBufferCount > 1e5) {
162
+ throw new Error("Performance event export maxBufferCount must be between 1 and 100000");
163
+ }
164
+ if (!Number.isSafeInteger(maxBufferBytes) || maxBufferBytes <= 0 || maxBufferBytes > 100 * 1024 * 1024) {
165
+ throw new Error("Performance event export maxBufferBytes must be between 1 and 104857600");
166
+ }
167
+ if (!Number.isSafeInteger(flushIntervalMs) || flushIntervalMs < 0 || flushIntervalMs > MAX_PERFORMANCE_TIMER_MS) {
168
+ throw new Error(`Performance flushIntervalMs must be between 0 and ${MAX_PERFORMANCE_TIMER_MS}`);
169
+ }
170
+ if (!Number.isInteger(retryAttempts) || retryAttempts < 0 || retryAttempts > 10) {
171
+ throw new Error("Performance event export retryAttempts must be between 0 and 10");
172
+ }
173
+ if (!Number.isSafeInteger(retryBaseDelayMs) || retryBaseDelayMs < 0 || retryBaseDelayMs > MAX_EXPORT_RETRY_DELAY_MS) {
174
+ throw new Error(`Performance retryBaseDelayMs must be between 0 and ${MAX_EXPORT_RETRY_DELAY_MS}`);
175
+ }
176
+ if (!Number.isSafeInteger(operationTimeoutMs) || operationTimeoutMs <= 0 || operationTimeoutMs > MAX_EXPORT_OPERATION_TIMEOUT_MS) {
177
+ throw new Error(`Performance operationTimeoutMs must be between 1 and ${MAX_EXPORT_OPERATION_TIMEOUT_MS}`);
178
+ }
179
+ if (operationTimeoutMs * (retryAttempts + 1) + retryBaseDelayMs * (retryAttempts * (retryAttempts + 1) / 2) > MAX_EXPORT_DELIVERY_BUDGET_MS) {
180
+ throw new Error(`Performance event export retry policy must fit within ${MAX_EXPORT_DELIVERY_BUDGET_MS}ms`);
181
+ }
182
+ const exporterNames = /* @__PURE__ */ new Set();
183
+ const exporters = [];
184
+ for (const configured of configuredEntries) {
185
+ const { name } = configured;
186
+ if (typeof name !== "string" || name.length > 64 || !/^[a-z][a-z0-9_.-]{0,63}$/i.test(name) || exporterNames.has(name)) {
187
+ throw new Error("Performance event exporter names must be unique safe identifiers");
188
+ }
189
+ exporters.push({ name, exporter: captureExporter(name, configured.exporter) });
190
+ exporterNames.add(name);
191
+ }
192
+ const logicalQueue = [];
193
+ let logicalQueueBytes = 0;
194
+ let partiallyCommittedRecords = 0;
195
+ let nextSequence = 0;
196
+ const exporterHealth = /* @__PURE__ */ new Map();
197
+ const terminalExporters = /* @__PURE__ */ new Set();
198
+ const completedExporterLifecycles = /* @__PURE__ */ new Set();
199
+ const pendingDeliveries = /* @__PURE__ */ new Map();
200
+ const pendingFlushHooks = /* @__PURE__ */ new Map();
201
+ const exportersNeedingFlush = /* @__PURE__ */ new Set();
202
+ const lifecycleOperations = createEventExporterLifecycleOperations();
203
+ for (const { name } of exporters) {
204
+ exporterHealth.set(name, { isHealthy: true, failures: 0 });
205
+ }
206
+ let droppedEvents = 0;
207
+ let retriedTotal = 0;
208
+ let activeRetries = 0;
209
+ let lastFailureCode;
210
+ let stopped = false;
211
+ let closing = false;
212
+ let flushPromise = null;
213
+ let requestedFlushGeneration = 0;
214
+ let completedFlushGeneration = 0;
215
+ let requestedExporterHooks = false;
216
+ let shutdownPromise = null;
217
+ let timer;
218
+ const errorsReport = captureErrorsReport(errors);
219
+ let pendingErrorReport;
220
+ let pendingObservation;
221
+ const reportError = (context) => {
222
+ if (pendingErrorReport) return;
223
+ pendingErrorReport = true;
224
+ try {
225
+ const result = errorsReport?.(normalizeError(new Error("performance_export_failed")), context);
226
+ ignoreRuntimePromiseRejection(result);
227
+ if (isRuntimePromise(result)) {
228
+ const reportPromise = result;
229
+ pendingErrorReport = reportPromise;
230
+ const release = () => {
231
+ if (pendingErrorReport === reportPromise) pendingErrorReport = void 0;
232
+ };
233
+ void Reflect.apply(Promise.prototype.then, reportPromise, [
234
+ release,
235
+ release
236
+ ]);
237
+ } else pendingErrorReport = void 0;
238
+ } catch {
239
+ }
240
+ };
241
+ const emit = (name, value, labels) => {
242
+ if (pendingObservation) return;
243
+ pendingObservation = true;
244
+ try {
245
+ const result = observe?.(name, value, labels);
246
+ ignoreRuntimePromiseRejection(result);
247
+ if (isRuntimePromise(result)) {
248
+ const observation = result;
249
+ pendingObservation = observation;
250
+ const release = () => {
251
+ if (pendingObservation === observation) pendingObservation = void 0;
252
+ };
253
+ try {
254
+ void Reflect.apply(Promise.prototype.then, observation, [release, release]);
255
+ } catch {
256
+ release();
257
+ }
258
+ } else pendingObservation = void 0;
259
+ } catch {
260
+ }
261
+ };
262
+ const setExporterFailure = (name, error) => {
263
+ const previous = exporterHealth.get(name) ?? { failures: 0 };
264
+ const safeError = getPerformanceExportErrorMetadata(error)?.code ?? "performance_export_failed";
265
+ lastFailureCode = KNOWN_EXPORT_FAILURE_CODE.test(safeError) ? safeError.toUpperCase() : "PERFORMANCE_EXPORT_FAILURE";
266
+ exporterHealth.set(name, {
267
+ isHealthy: false,
268
+ failures: previous.failures + 1
269
+ });
270
+ emit("_performance_export_failures_total", 1);
271
+ reportError({ stage: "performance", operation: "event_export", exporter: name });
272
+ };
273
+ const setExporterSuccess = (name) => {
274
+ const previous = exporterHealth.get(name) ?? { failures: 0 };
275
+ exporterHealth.set(name, {
276
+ isHealthy: true,
277
+ failures: previous.failures
278
+ });
279
+ if ([...exporterHealth.values()].every(({ isHealthy }) => isHealthy)) lastFailureCode = void 0;
280
+ };
281
+ const getActiveExporterNames = () => exporters.map(({ name }) => name).filter((name) => !terminalExporters.has(name));
282
+ const countPending = (name, through) => logicalQueue.filter((item) => item.id <= through && item.pending.has(name)).length;
283
+ const pruneCommitted = () => {
284
+ let committedCount = 0;
285
+ let committedBytes = 0;
286
+ while (logicalQueue[committedCount]?.pending.size === 0) {
287
+ committedBytes += logicalQueue[committedCount].bytes;
288
+ committedCount += 1;
289
+ }
290
+ if (committedCount > 0) {
291
+ logicalQueue.splice(0, committedCount);
292
+ logicalQueueBytes -= committedBytes;
293
+ }
294
+ };
295
+ const removePendingDestination = (item, name) => {
296
+ if (!item.pending.delete(name)) return false;
297
+ if (item.pending.size > 0 && !item.partial) {
298
+ item.partial = true;
299
+ partiallyCommittedRecords += 1;
300
+ } else if (item.pending.size === 0 && item.partial) {
301
+ item.partial = false;
302
+ partiallyCommittedRecords -= 1;
303
+ }
304
+ return true;
305
+ };
306
+ const abandonDestination = (name) => {
307
+ let terminalDrops = 0;
308
+ for (const item of logicalQueue) {
309
+ if (removePendingDestination(item, name)) terminalDrops += 1;
310
+ }
311
+ pruneCommitted();
312
+ droppedEvents += terminalDrops;
313
+ if (terminalDrops > 0) emit(DROPPED_TOTAL_METRIC, terminalDrops, { reason: "terminal_exporter_error" });
314
+ emit(QUEUE_SIZE_METRIC, logicalQueue.length);
315
+ };
316
+ const terminateDestination = (name, error) => {
317
+ if (terminalExporters.has(name)) return;
318
+ terminalExporters.add(name);
319
+ setExporterFailure(name, error);
320
+ abandonDestination(name);
321
+ };
322
+ const releasePartiallyCommittedRecord = (activeDestinationCount) => {
323
+ if (partiallyCommittedRecords === 0 || activeDestinationCount < 2) return false;
324
+ const index = logicalQueue.findIndex((item) => item.partial && item.pending.size < activeDestinationCount);
325
+ if (index < 0) return false;
326
+ const releasedBytes = logicalQueue[index].bytes;
327
+ logicalQueue[index].partial = false;
328
+ partiallyCommittedRecords -= 1;
329
+ logicalQueue.splice(index, 1);
330
+ logicalQueueBytes -= releasedBytes;
331
+ droppedEvents += 1;
332
+ emit(DROPPED_TOTAL_METRIC, 1, { reason: "partial_fanout_backpressure" });
333
+ emit(QUEUE_SIZE_METRIC, logicalQueue.length);
334
+ return true;
335
+ };
336
+ const deliverBatch = (name, exporter, through) => {
337
+ const existing = pendingDeliveries.get(name);
338
+ if (existing) return existing;
339
+ const items = [];
340
+ let batchBytes = 0;
341
+ for (const item of logicalQueue) {
342
+ if (item.id > through) break;
343
+ if (!item.pending.has(name)) continue;
344
+ if (items.length >= MAX_PERFORMANCE_EXPORT_BATCH_COUNT || batchBytes + item.bytes > MAX_PERFORMANCE_EXPORT_BATCH_BYTES) break;
345
+ items.push(item);
346
+ batchBytes += item.bytes;
347
+ }
348
+ const attemptBatch = deepFreezePerformanceValue(
349
+ items.map((item) => JSON.parse(item.serialized))
350
+ );
351
+ const operation = Promise.resolve().then(async () => await exporter.export(attemptBatch)).then(() => {
352
+ setExporterSuccess(name);
353
+ if (exporter.flush) exportersNeedingFlush.add(name);
354
+ for (const item of items) removePendingDestination(item, name);
355
+ pruneCommitted();
356
+ emit(QUEUE_SIZE_METRIC, logicalQueue.length);
357
+ if (pendingDeliveries.get(name) === operation) pendingDeliveries.delete(name);
358
+ });
359
+ pendingDeliveries.set(name, operation);
360
+ void operation.catch((error) => {
361
+ if (pendingDeliveries.get(name) === operation) pendingDeliveries.delete(name);
362
+ const metadata = getPerformanceExportErrorMetadata(error);
363
+ if (metadata?.retryable === false) terminateDestination(name, error);
364
+ });
365
+ return operation;
366
+ };
367
+ const runExporterFlush = (name, exporter) => {
368
+ const existing = pendingFlushHooks.get(name);
369
+ if (existing) return existing;
370
+ const operation = Promise.resolve().then(async () => await exporter.flush?.());
371
+ pendingFlushHooks.set(name, operation);
372
+ void operation.then(
373
+ () => pendingFlushHooks.delete(name),
374
+ () => pendingFlushHooks.delete(name)
375
+ );
376
+ return operation;
377
+ };
378
+ const runFlushCycle = async (flushExporterHooks) => {
379
+ if (stopped || exporters.length === 0) {
380
+ return;
381
+ }
382
+ const failures = [];
383
+ await Promise.all(exporters.map(async ({ name, exporter }) => {
384
+ if (terminalExporters.has(name)) {
385
+ return;
386
+ }
387
+ const boundary = logicalQueue.at(-1)?.id ?? -1;
388
+ let remainingBatchCount = countPending(name, boundary);
389
+ let deliverySucceeded = remainingBatchCount === 0;
390
+ let failedAttempts = 0;
391
+ while (remainingBatchCount > 0) {
392
+ if (terminalExporters.has(name)) {
393
+ failures.push(new Error("Exporter failed"));
394
+ break;
395
+ }
396
+ remainingBatchCount = countPending(name, boundary);
397
+ if (remainingBatchCount === 0) {
398
+ deliverySucceeded = true;
399
+ break;
400
+ }
401
+ let delivery;
402
+ try {
403
+ delivery = deliverBatch(name, exporter, boundary);
404
+ await withPerformanceExportTimeout(
405
+ delivery,
406
+ operationTimeoutMs,
407
+ `Exporter "${name}"`
408
+ );
409
+ remainingBatchCount = countPending(name, boundary);
410
+ deliverySucceeded = remainingBatchCount === 0;
411
+ failedAttempts = 0;
412
+ } catch (error) {
413
+ const errorMetadata = getPerformanceExportErrorMetadata(error);
414
+ if (errorMetadata?.code !== "performance_export_timeout" && delivery && pendingDeliveries.get(name) === delivery) {
415
+ pendingDeliveries.delete(name);
416
+ }
417
+ const retryable = errorMetadata?.retryable ?? true;
418
+ if (!retryable || failedAttempts >= retryAttempts) {
419
+ if (retryable) setExporterFailure(name, error);
420
+ else terminateDestination(name, error);
421
+ failures.push(error);
422
+ break;
423
+ }
424
+ failedAttempts += 1;
425
+ retriedTotal += 1;
426
+ emit("_performance_export_retries_total", 1);
427
+ activeRetries += 1;
428
+ try {
429
+ await sleep(retryBaseDelayMs * failedAttempts);
430
+ } finally {
431
+ activeRetries -= 1;
432
+ }
433
+ }
434
+ }
435
+ if (!deliverySucceeded || !flushExporterHooks || !exporter.flush || !exportersNeedingFlush.has(name)) return;
436
+ for (let attempt = 0; attempt <= retryAttempts; attempt += 1) {
437
+ try {
438
+ await withPerformanceExportTimeout(
439
+ runExporterFlush(name, exporter),
440
+ operationTimeoutMs,
441
+ `Exporter "${name}" flush`
442
+ );
443
+ setExporterSuccess(name);
444
+ exportersNeedingFlush.delete(name);
445
+ break;
446
+ } catch (error) {
447
+ if (attempt >= retryAttempts) {
448
+ setExporterFailure(name, error);
449
+ failures.push(error);
450
+ break;
451
+ }
452
+ retriedTotal += 1;
453
+ emit("_performance_export_retries_total", 1);
454
+ activeRetries += 1;
455
+ try {
456
+ await sleep(retryBaseDelayMs * (attempt + 1));
457
+ } finally {
458
+ activeRetries -= 1;
459
+ }
460
+ }
461
+ }
462
+ })).then(() => {
463
+ if (failures.length > 0) {
464
+ throw new AggregateError(failures, "Performance event export flush failed");
465
+ }
466
+ });
467
+ };
468
+ const flush = async (flushExporterHooks = true) => {
469
+ if (stopped || exporters.length === 0) return;
470
+ requestedFlushGeneration = Math.max(
471
+ requestedFlushGeneration,
472
+ completedFlushGeneration + (flushPromise ? 2 : 1)
473
+ );
474
+ requestedExporterHooks ||= flushExporterHooks;
475
+ if (!flushPromise) {
476
+ flushPromise = (async () => {
477
+ while (completedFlushGeneration < requestedFlushGeneration) {
478
+ const targetGeneration = requestedFlushGeneration;
479
+ const runExporterHooks = requestedExporterHooks;
480
+ requestedExporterHooks = false;
481
+ await runFlushCycle(runExporterHooks);
482
+ completedFlushGeneration = targetGeneration;
483
+ }
484
+ })().finally(() => {
485
+ flushPromise = null;
486
+ });
487
+ }
488
+ return flushPromise;
489
+ };
490
+ if (exporters.length > 0 && flushIntervalMs > 0) {
491
+ timer = setInterval(() => {
492
+ void flush().catch(() => reportError({
493
+ stage: "performance",
494
+ operation: "event_export_timer_flush"
495
+ }));
496
+ }, flushIntervalMs);
497
+ try {
498
+ timer.unref?.();
499
+ } catch {
500
+ }
501
+ }
502
+ return {
503
+ enqueue(record) {
504
+ if (stopped || closing || exporters.length === 0) {
505
+ return;
506
+ }
507
+ const activeNames = getActiveExporterNames();
508
+ if (activeNames.length === 0) {
509
+ droppedEvents += 1;
510
+ emit(DROPPED_TOTAL_METRIC, 1, { reason: "terminal_exporter_unavailable" });
511
+ return;
512
+ }
513
+ const saturatedReason = logicalQueue.length >= maxBufferCount ? "count_limit" : logicalQueueBytes >= maxBufferBytes ? "byte_limit" : void 0;
514
+ if (saturatedReason && (partiallyCommittedRecords === 0 || activeNames.length < 2)) {
515
+ droppedEvents += 1;
516
+ emit(DROPPED_TOTAL_METRIC, 1, { reason: saturatedReason });
517
+ return;
518
+ }
519
+ const snapshot = serializePerformanceEventRecord(record);
520
+ if (snapshot === null || snapshot.bytes > MAX_PERFORMANCE_EXPORT_BATCH_BYTES) {
521
+ droppedEvents += 1;
522
+ emit(DROPPED_TOTAL_METRIC, 1, {
523
+ reason: snapshot ? "record_size_limit" : "serialization_error"
524
+ });
525
+ if (!snapshot) reportError({
526
+ stage: "performance",
527
+ operation: "event_export_enqueue"
528
+ });
529
+ return;
530
+ }
531
+ let dropReason = logicalQueue.length >= maxBufferCount ? "count_limit" : logicalQueueBytes + snapshot.bytes > maxBufferBytes ? "byte_limit" : void 0;
532
+ while (dropReason && releasePartiallyCommittedRecord(activeNames.length)) {
533
+ dropReason = logicalQueue.length >= maxBufferCount ? "count_limit" : logicalQueueBytes + snapshot.bytes > maxBufferBytes ? "byte_limit" : void 0;
534
+ }
535
+ if (dropReason) {
536
+ droppedEvents += 1;
537
+ emit(DROPPED_TOTAL_METRIC, 1, { reason: dropReason });
538
+ return;
539
+ }
540
+ logicalQueue.push({
541
+ serialized: snapshot.serialized,
542
+ bytes: snapshot.bytes,
543
+ pending: new Set(activeNames),
544
+ partial: false,
545
+ id: nextSequence++
546
+ });
547
+ logicalQueueBytes += snapshot.bytes;
548
+ emit(QUEUE_SIZE_METRIC, logicalQueue.length);
549
+ },
550
+ flush,
551
+ async shutdown() {
552
+ if (shutdownPromise) {
553
+ return shutdownPromise;
554
+ }
555
+ shutdownPromise = (async () => {
556
+ closing = true;
557
+ if (timer !== void 0) {
558
+ const activeTimer = timer;
559
+ timer = void 0;
560
+ try {
561
+ clearInterval(activeTimer);
562
+ } catch {
563
+ }
564
+ }
565
+ const failures = [];
566
+ try {
567
+ await flush(false);
568
+ } catch (error) {
569
+ failures.push(error);
570
+ }
571
+ const undelivered = logicalQueue.length;
572
+ if (undelivered > 0) {
573
+ failures.push(new Error(`Performance event exporter shutdown left ${undelivered} event(s) undelivered`));
574
+ throw new AggregateError(failures, "Performance event exporter shutdown failed");
575
+ }
576
+ await Promise.all(exporters.map(async ({ name, exporter }) => {
577
+ if (completedExporterLifecycles.has(name)) return;
578
+ let failed = false;
579
+ if (exporter.flush) {
580
+ try {
581
+ await withPerformanceExportTimeout(
582
+ lifecycleOperations.run(name, "flush", () => runExporterFlush(name, exporter)),
583
+ operationTimeoutMs,
584
+ "Exporter flush"
585
+ );
586
+ } catch (error) {
587
+ failed = true;
588
+ failures.push(error);
589
+ }
590
+ }
591
+ let shutdownCompleted = false;
592
+ if (exporter.shutdown) {
593
+ try {
594
+ await withPerformanceExportTimeout(
595
+ lifecycleOperations.run(name, "shutdown", () => exporter.shutdown?.()),
596
+ operationTimeoutMs,
597
+ "Exporter shutdown"
598
+ );
599
+ shutdownCompleted = true;
600
+ } catch (error) {
601
+ failed = true;
602
+ failures.push(error);
603
+ }
604
+ }
605
+ if (!failed || shutdownCompleted) completedExporterLifecycles.add(name);
606
+ }));
607
+ if (failures.length > 0) {
608
+ throw new AggregateError(failures, "Performance event exporter shutdown failed");
609
+ }
610
+ stopped = true;
611
+ })().catch((error) => {
612
+ shutdownPromise = null;
613
+ throw error;
614
+ });
615
+ return shutdownPromise;
616
+ },
617
+ getStatus() {
618
+ const unhealthy = terminalExporters.size > 0 || [...exporterHealth.values()].some(({ isHealthy }) => !isHealthy);
619
+ return Object.freeze({
620
+ queueSize: logicalQueue.length,
621
+ droppedTotal: droppedEvents,
622
+ retriedTotal,
623
+ sinkState: stopped ? "closed" : unhealthy ? "unhealthy" : activeRetries > 0 ? "degraded" : "healthy",
624
+ ...lastFailureCode ? { lastFailureCode } : {}
625
+ });
626
+ }
627
+ };
628
+ }
629
+
630
+ export { createEventExportManager };
631
+ //# sourceMappingURL=event-export-manager-DEB64LWZ.js.map
632
+ //# sourceMappingURL=event-export-manager-DEB64LWZ.js.map