@loadstrike/loadstrike-sdk 1.0.31001 → 1.0.32601

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 (45) hide show
  1. package/README.md +16 -2
  2. package/dist/cjs/internal/prometheus-remote-write.js +37 -0
  3. package/dist/cjs/internal/reporting-sink-http-error.js +17 -0
  4. package/dist/cjs/internal/vendor-metric-payloads.js +390 -0
  5. package/dist/cjs/iteration-observations.js +24 -8
  6. package/dist/cjs/local-report-input.js +21 -0
  7. package/dist/cjs/local.js +48 -63
  8. package/dist/cjs/report-history.js +421 -0
  9. package/dist/cjs/reporting-containment.js +242 -0
  10. package/dist/cjs/reporting-svg.js +116 -0
  11. package/dist/cjs/reporting.js +413 -136
  12. package/dist/cjs/runtime.js +237 -8
  13. package/dist/cjs/sinks.js +1337 -38
  14. package/dist/cjs/transports.js +1339 -151
  15. package/dist/esm/internal/prometheus-remote-write.js +31 -0
  16. package/dist/esm/internal/reporting-sink-http-error.js +13 -0
  17. package/dist/esm/internal/vendor-metric-payloads.js +382 -0
  18. package/dist/esm/iteration-observations.js +24 -8
  19. package/dist/esm/local-report-input.js +17 -0
  20. package/dist/esm/local.js +49 -64
  21. package/dist/esm/report-history.js +413 -0
  22. package/dist/esm/reporting-containment.js +238 -0
  23. package/dist/esm/reporting-svg.js +113 -0
  24. package/dist/esm/reporting.js +413 -136
  25. package/dist/esm/runtime.js +239 -10
  26. package/dist/esm/sinks.js +1334 -35
  27. package/dist/esm/transports.js +1335 -151
  28. package/dist/types/contracts.d.ts +1 -0
  29. package/dist/types/index.d.ts +1 -1
  30. package/dist/types/internal/prometheus-remote-write.d.ts +2 -0
  31. package/dist/types/internal/reporting-sink-http-error.d.ts +6 -0
  32. package/dist/types/internal/vendor-metric-payloads.d.ts +48 -0
  33. package/dist/types/local-report-input.d.ts +6 -0
  34. package/dist/types/local.d.ts +0 -6
  35. package/dist/types/report-history.d.ts +124 -0
  36. package/dist/types/reporting-containment.d.ts +2 -0
  37. package/dist/types/reporting-svg.d.ts +2 -0
  38. package/dist/types/reporting.d.ts +6 -3
  39. package/dist/types/runtime.d.ts +24 -0
  40. package/dist/types/sinks.d.ts +134 -17
  41. package/dist/types/transports.d.ts +2 -0
  42. package/package.json +9 -3
  43. package/dist/cjs/internal-build.js +0 -4
  44. package/dist/esm/internal-build.js +0 -1
  45. package/dist/types/internal-build.d.ts +0 -1
package/dist/cjs/local.js CHANGED
@@ -55,9 +55,8 @@ const childProcess = __importStar(require("node:child_process"));
55
55
  const node_crypto_1 = require("node:crypto");
56
56
  const correlation_js_1 = require("./correlation.js");
57
57
  const transports_js_1 = require("./transports.js");
58
- const internal_build_js_1 = require("./internal-build.js");
58
+ const reporting_containment_js_1 = require("./reporting-containment.js");
59
59
  const DEFAULT_LICENSING_API_BASE_URL = "https://licensing.loadstrike.com";
60
- let developmentLicensingApiBaseUrlOverride;
61
60
  const BUILT_IN_WORKER_PLUGIN_NAMES = new Set([
62
61
  "loadstrike failed responses",
63
62
  "loadstrike correlation"
@@ -111,7 +110,7 @@ class LoadStrikeLocalClient {
111
110
  _LoadStrikeLocalClient_signingKeyCache.set(this, new Map());
112
111
  _LoadStrikeLocalClient_heartbeatDrains.set(this, new WeakMap());
113
112
  assertNoDisableLicenseEnforcementOption(options, "LoadStrikeLocalClient");
114
- __classPrivateFieldSet(this, _LoadStrikeLocalClient_licensingApiBaseUrl, resolveLicensingApiBaseUrl(), "f");
113
+ __classPrivateFieldSet(this, _LoadStrikeLocalClient_licensingApiBaseUrl, DEFAULT_LICENSING_API_BASE_URL, "f");
115
114
  this.licenseValidationTimeoutMs = normalizeTimeoutMs(options.licenseValidationTimeoutMs);
116
115
  }
117
116
  portalReportingIngestUrl() {
@@ -119,6 +118,7 @@ class LoadStrikeLocalClient {
119
118
  }
120
119
  async run(request) {
121
120
  const sanitized = sanitizeRequest(request);
121
+ validateContainmentRequest(sanitized);
122
122
  const licenseSession = await this.acquireLicenseLease(sanitized);
123
123
  try {
124
124
  const createdUtc = new Date();
@@ -574,66 +574,27 @@ function sanitizeRequest(request) {
574
574
  RunArgs: runArgs
575
575
  };
576
576
  }
577
- function normalizeLicensingApiBaseUrl(value) {
578
- const normalized = (value ?? "").trim();
579
- return normalized || DEFAULT_LICENSING_API_BASE_URL;
580
- }
581
- function resolveLicensingApiBaseUrl() {
582
- return normalizeLicensingApiBaseUrl(developmentLicensingApiBaseUrlOverride ?? resolveInternalBlackBoxLicensingApiBaseUrl());
583
- }
584
- function setDevelopmentLicensingApiBaseUrlOverride(value) {
585
- if (value != null && value.trim()) {
586
- assertInternalTestHarnessLicensingOverride();
587
- const normalized = normalizeLicensingApiBaseUrl(value);
588
- if (!isLoopbackHttpBaseUrl(normalized)) {
589
- throw new TypeError("Internal development licensing API overrides must use a loopback http URL.");
577
+ function validateContainmentRequest(request) {
578
+ const scenarios = Array.isArray(request.Scenarios) ? request.Scenarios : [];
579
+ for (const scenarioValue of scenarios) {
580
+ const scenario = asRecord(scenarioValue);
581
+ const tracking = asRecord(pickValue(scenario, "Tracking", "tracking"));
582
+ for (const field of ["Source", "Destination"]) {
583
+ const endpoint = asRecord(pickValue(tracking, field, field.toLowerCase()));
584
+ if (Object.keys(endpoint).length > 0) {
585
+ (0, transports_js_1.validateNativeEndpointExecutionSupport)(endpoint);
586
+ }
590
587
  }
591
- developmentLicensingApiBaseUrlOverride = normalized;
592
- return;
593
- }
594
- if (value != null) {
595
- assertInternalTestHarnessLicensingOverride();
596
- }
597
- developmentLicensingApiBaseUrlOverride = undefined;
598
- }
599
- function assertInternalTestHarnessLicensingOverride() {
600
- if (isInternalTestHarnessCall()) {
601
- return;
602
- }
603
- throw new TypeError("Internal development licensing API override is available only to LoadStrike SDK tests.");
604
- }
605
- function isInternalTestHarnessCall() {
606
- const stack = String(new Error().stack ?? "").replace(/\\/g, "/").toLowerCase();
607
- return stack.includes("/sdk/ts/tests/");
608
- }
609
- function isLoopbackHttpBaseUrl(value) {
610
- let parsed;
611
- try {
612
- parsed = new URL(value);
613
- }
614
- catch {
615
- return false;
616
- }
617
- if (parsed.protocol !== "http:") {
618
- return false;
619
588
  }
620
- const host = parsed.hostname.trim().toLowerCase();
621
- return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1" || host.endsWith(".localhost");
622
- }
623
- function resolveInternalBlackBoxLicensingApiBaseUrl() {
624
- const compiledToken = String(internal_build_js_1.BLACKBOX_LICENSING_API_BASE_URL_TOKEN ?? "").trim();
625
- if (!compiledToken) {
626
- return undefined;
627
- }
628
- const envToken = String(process.env.LOADSTRIKE_INTERNAL_BLACKBOX_API_BASE_URL_TOKEN ?? "").trim();
629
- if (!envToken || envToken !== compiledToken) {
630
- return undefined;
631
- }
632
- const candidate = normalizeLicensingApiBaseUrl(process.env.LOADSTRIKE_INTERNAL_BLACKBOX_API_BASE_URL);
633
- if (!isLoopbackHttpBaseUrl(candidate)) {
634
- return undefined;
589
+ const context = asRecord(request.Context);
590
+ const reportingSinks = asList(pickValue(context, "ReportingSinks", "reportingSinks"));
591
+ for (const [key, value] of Object.entries(context)) {
592
+ const normalizedKey = key.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
593
+ if (normalizedKey === "infraconfig") {
594
+ (0, reporting_containment_js_1.assertNoUnsupportedReportingInfraConfig)(value, reportingSinks);
595
+ }
635
596
  }
636
- return candidate;
597
+ (0, reporting_containment_js_1.assertNoUnsupportedReportingSinkGraph)(reportingSinks);
637
598
  }
638
599
  function normalizeTimeoutMs(value) {
639
600
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
@@ -1341,6 +1302,20 @@ function mapEndpointSpec(spec, useLoadStrikeTraceIdHeader = false) {
1341
1302
  redisStreams: asRecord(pickValue(spec, "RedisStreams", "redisStreams")),
1342
1303
  azureEventHubs: asRecord(pickValue(spec, "AzureEventHubs", "azureEventHubs")),
1343
1304
  pushDiffusion: asRecord(pickValue(spec, "PushDiffusion", "pushDiffusion")),
1305
+ grpc: mapEndpointProtocolOptions(spec, ["Grpc", "grpc"], [
1306
+ "Target", "target", "ServiceName", "serviceName", "MethodName", "methodName",
1307
+ "MethodType", "methodType", "Deadline", "deadline", "DeadlineSeconds", "deadlineSeconds",
1308
+ "DeadlineMs", "deadlineMs", "Metadata", "metadata", "Produce", "produce", "Consume", "consume",
1309
+ "ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync", "ConnectionMetadata", "connectionMetadata",
1310
+ "NativeClient", "nativeClient"
1311
+ ]),
1312
+ webSocket: mapEndpointProtocolOptions(spec, ["WebSocket", "webSocket"], [
1313
+ "Url", "url", "Subprotocols", "subprotocols", "ConnectTimeout", "connectTimeout",
1314
+ "ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeoutMs", "connectTimeoutMs",
1315
+ "CloseTimeout", "closeTimeout", "CloseTimeoutSeconds", "closeTimeoutSeconds", "CloseTimeoutMs", "closeTimeoutMs",
1316
+ "Produce", "produce", "Consume", "consume", "ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync",
1317
+ "ConnectionMetadata", "connectionMetadata", "NativeClient", "nativeClient"
1318
+ ]),
1344
1319
  delegate: (typeof delegateProduce === "function"
1345
1320
  || typeof delegateConsume === "function"
1346
1321
  || typeof delegateProduceAsync === "function"
@@ -1363,6 +1338,19 @@ function mapEndpointSpec(spec, useLoadStrikeTraceIdHeader = false) {
1363
1338
  : undefined
1364
1339
  };
1365
1340
  }
1341
+ function mapEndpointProtocolOptions(spec, nestedKeys, flatKeys) {
1342
+ const nested = asRecord(pickValue(spec, ...nestedKeys));
1343
+ if (Object.keys(nested).length > 0) {
1344
+ return nested;
1345
+ }
1346
+ const options = {};
1347
+ for (const key of flatKeys) {
1348
+ if (Object.prototype.hasOwnProperty.call(spec, key)) {
1349
+ options[key] = spec[key];
1350
+ }
1351
+ }
1352
+ return options;
1353
+ }
1366
1354
  function normalizePayload(payload, endpoint, index) {
1367
1355
  const resolvedBody = payload?.body ?? endpoint.messagePayload;
1368
1356
  const normalized = {
@@ -2319,8 +2307,6 @@ exports.__loadstrikeTestExports = {
2319
2307
  mapEnvironmentClassification,
2320
2308
  mapEndpointSpec,
2321
2309
  normalizeEndpointPayloadValue,
2322
- normalizeLicensingApiBaseUrl,
2323
- resolveLicensingApiBaseUrl,
2324
2310
  normalizeNodeType,
2325
2311
  normalizePayload,
2326
2312
  normalizeStringArray,
@@ -2339,7 +2325,6 @@ exports.__loadstrikeTestExports = {
2339
2325
  sanitizeLooseJson,
2340
2326
  sanitizeRequest,
2341
2327
  setJsonPathValue,
2342
- setDevelopmentLicensingApiBaseUrlOverride,
2343
2328
  sleep,
2344
2329
  stringOrDefault,
2345
2330
  toBoolean,
@@ -0,0 +1,421 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReportHistoryWorker = exports.ReportHistoryLifecycleCoordinator = exports.ReportHistoryCollector = exports.REPORT_HISTORY_MAX_SCALAR_VALUES = exports.REPORT_HISTORY_MAX_POINTS = void 0;
4
+ exports.sanitizedExceptionClassChain = sanitizedExceptionClassChain;
5
+ exports.deriveScenarioRates = deriveScenarioRates;
6
+ exports.REPORT_HISTORY_MAX_POINTS = 2048;
7
+ exports.REPORT_HISTORY_MAX_SCALAR_VALUES = 262144;
8
+ /**
9
+ * Bounded report-only cumulative telemetry. This type is intentionally not
10
+ * exported from the package entry point and never participates in public run,
11
+ * sink, observation, or cluster payloads.
12
+ */
13
+ class ReportHistoryCollector {
14
+ constructor(options) {
15
+ this.points = [];
16
+ this.terminalCaptured = false;
17
+ const scenarioCount = Math.max(1, Math.trunc(options.scenarioCount));
18
+ const scalarValuesPerPoint = scenarioCount * 16;
19
+ const budgetPointLimit = Math.floor(exports.REPORT_HISTORY_MAX_SCALAR_VALUES / scalarValuesPerPoint);
20
+ if (!Number.isSafeInteger(scalarValuesPerPoint) || budgetPointLimit < 2) {
21
+ this.pointLimit = 0;
22
+ this.reasonCategory = "budget_pressure";
23
+ }
24
+ else {
25
+ const requestedLimit = options.maxPoints === undefined
26
+ ? exports.REPORT_HISTORY_MAX_POINTS
27
+ : Math.max(2, Math.trunc(options.maxPoints));
28
+ this.pointLimit = Math.min(exports.REPORT_HISTORY_MAX_POINTS, requestedLimit, Math.max(2, budgetPointLimit));
29
+ }
30
+ this.nowNs = options.nowNs ?? (() => process.hrtime.bigint());
31
+ this.onCaptureError = options.onCaptureError;
32
+ }
33
+ get available() {
34
+ return this.reasonCategory === undefined;
35
+ }
36
+ start(startNs = this.nowNs()) {
37
+ if (!this.available || this.startNs !== undefined) {
38
+ return;
39
+ }
40
+ this.startNs = startNs;
41
+ }
42
+ capture(snapshotFactory) {
43
+ return this.captureSafely(snapshotFactory, false);
44
+ }
45
+ finalize(snapshotFactory) {
46
+ return this.captureSafely(snapshotFactory, true);
47
+ }
48
+ disable(reasonCategory) {
49
+ this.reasonCategory = reasonCategory;
50
+ this.points = [];
51
+ }
52
+ toProjection() {
53
+ if (this.reasonCategory !== undefined) {
54
+ return {
55
+ status: "unavailable",
56
+ reasonCategory: this.reasonCategory,
57
+ points: []
58
+ };
59
+ }
60
+ return {
61
+ status: "available",
62
+ points: this.points.map(clonePoint)
63
+ };
64
+ }
65
+ captureSafely(snapshotFactory, terminal) {
66
+ if (!this.available || (this.terminalCaptured && !terminal)) {
67
+ return false;
68
+ }
69
+ try {
70
+ if (this.startNs === undefined) {
71
+ this.start();
72
+ }
73
+ const nowNs = this.nowNs();
74
+ const elapsedNs = nowNs > this.startNs ? nowNs - this.startNs : 0n;
75
+ const elapsedSeconds = Number(elapsedNs) / 1000000000;
76
+ const scenarios = [...snapshotFactory()]
77
+ .map(projectScenario)
78
+ .sort((left, right) => (left.sortIndex ?? 0) - (right.sortIndex ?? 0)
79
+ || left.scenarioName.localeCompare(right.scenarioName));
80
+ const point = {
81
+ elapsedSeconds: finiteNonNegative(elapsedSeconds) ?? 0,
82
+ terminal,
83
+ scenarios
84
+ };
85
+ if (terminal) {
86
+ this.points = this.points.filter((existing) => existing.elapsedSeconds <= point.elapsedSeconds);
87
+ this.terminalCaptured = true;
88
+ }
89
+ const existingIndex = this.points.findIndex((existing) => existing.elapsedSeconds === point.elapsedSeconds);
90
+ if (existingIndex >= 0) {
91
+ if (terminal || !this.points[existingIndex].terminal) {
92
+ this.points[existingIndex] = point;
93
+ }
94
+ }
95
+ else if (point.elapsedSeconds > 0 || terminal) {
96
+ this.points.push(point);
97
+ this.points.sort((left, right) => left.elapsedSeconds - right.elapsedSeconds);
98
+ }
99
+ this.compact();
100
+ return true;
101
+ }
102
+ catch (error) {
103
+ this.disable("capture_failure");
104
+ try {
105
+ this.onCaptureError?.(error);
106
+ }
107
+ catch {
108
+ // Report diagnostics are best-effort and must never affect the run.
109
+ }
110
+ return false;
111
+ }
112
+ }
113
+ compact() {
114
+ if (this.pointLimit === 2 && this.points.length > 2) {
115
+ this.points = [this.points[0], this.points[this.points.length - 1]];
116
+ return;
117
+ }
118
+ while (this.points.length > this.pointLimit) {
119
+ const newest = this.points[this.points.length - 1];
120
+ const thinned = [this.points[0]];
121
+ for (let index = 1; index < this.points.length - 1; index += 2) {
122
+ thinned.push(this.points[index]);
123
+ }
124
+ if (thinned[thinned.length - 1] !== newest) {
125
+ thinned.push(newest);
126
+ }
127
+ this.points = thinned;
128
+ }
129
+ }
130
+ }
131
+ exports.ReportHistoryCollector = ReportHistoryCollector;
132
+ /**
133
+ * Coordinates one private history worker across every local scenario. The
134
+ * first measured phase establishes the history clock, and the last scenario
135
+ * leaving execution records the terminal point before scenario cleanup.
136
+ */
137
+ class ReportHistoryLifecycleCoordinator {
138
+ constructor(worker) {
139
+ this.worker = worker;
140
+ this.measuredLoadStarted = false;
141
+ this.endedScenarios = 0;
142
+ this.completed = false;
143
+ this.terminalBoundaryReleased = false;
144
+ this.scenarioCount = Math.max(1, Math.trunc(worker.scenarioCount ?? 1));
145
+ this.terminalBoundary = new Promise((resolve) => {
146
+ this.releaseTerminalBoundary = resolve;
147
+ });
148
+ }
149
+ scenarioBombingStarted() {
150
+ if (this.completed || this.measuredLoadStarted) {
151
+ return;
152
+ }
153
+ this.measuredLoadStarted = true;
154
+ try {
155
+ this.worker.start();
156
+ }
157
+ catch {
158
+ // The private report-only worker cannot fail the measured run.
159
+ }
160
+ }
161
+ async scenarioExecutionEnded() {
162
+ if (this.completed) {
163
+ await this.terminalBoundary;
164
+ return;
165
+ }
166
+ this.endedScenarios += 1;
167
+ if (this.endedScenarios < this.scenarioCount) {
168
+ await this.terminalBoundary;
169
+ return;
170
+ }
171
+ this.completed = true;
172
+ try {
173
+ if (this.measuredLoadStarted) {
174
+ this.worker.stopAndFinalize();
175
+ }
176
+ }
177
+ catch {
178
+ // The private report-only worker cannot fail scenario completion.
179
+ }
180
+ finally {
181
+ this.releaseBoundary();
182
+ }
183
+ await this.terminalBoundary;
184
+ }
185
+ stopWithoutFinalizing() {
186
+ if (this.completed) {
187
+ this.releaseBoundary();
188
+ return;
189
+ }
190
+ this.completed = true;
191
+ try {
192
+ if (this.measuredLoadStarted) {
193
+ this.worker.stopWithoutFinalizing();
194
+ }
195
+ }
196
+ catch {
197
+ // The private report-only worker cannot fail run cleanup.
198
+ }
199
+ finally {
200
+ this.releaseBoundary();
201
+ }
202
+ }
203
+ releaseBoundary() {
204
+ if (!this.terminalBoundaryReleased) {
205
+ this.terminalBoundaryReleased = true;
206
+ this.releaseTerminalBoundary();
207
+ }
208
+ }
209
+ }
210
+ exports.ReportHistoryLifecycleCoordinator = ReportHistoryLifecycleCoordinator;
211
+ /**
212
+ * Owns the report timer. Every cadence calculation, timeout callback, snapshot,
213
+ * and projection is exception-bounded so report history can never fail a run.
214
+ */
215
+ class ReportHistoryWorker {
216
+ constructor(options) {
217
+ this.options = options;
218
+ this.cadenceNs = 0n;
219
+ this.nextDeadlineNs = 0n;
220
+ this.running = false;
221
+ this.nowNs = options.nowNs ?? (() => process.hrtime.bigint());
222
+ }
223
+ get started() {
224
+ return this.running;
225
+ }
226
+ start() {
227
+ if (this.running || !this.options.collector.available) {
228
+ return;
229
+ }
230
+ try {
231
+ this.cadenceNs = cadenceNanoseconds(this.options.cadenceSeconds);
232
+ const nowNs = this.nowNs();
233
+ this.options.collector.start(nowNs);
234
+ this.nextDeadlineNs = nowNs + this.cadenceNs;
235
+ this.running = true;
236
+ this.scheduleNext();
237
+ }
238
+ catch (error) {
239
+ this.fail(error);
240
+ }
241
+ }
242
+ stopAndFinalize() {
243
+ if (!this.running) {
244
+ return;
245
+ }
246
+ this.running = false;
247
+ if (this.timer !== undefined) {
248
+ clearTimeout(this.timer);
249
+ this.timer = undefined;
250
+ }
251
+ try {
252
+ this.options.collector.finalize(this.options.snapshotFactory);
253
+ }
254
+ catch (error) {
255
+ this.fail(error);
256
+ }
257
+ }
258
+ stopWithoutFinalizing() {
259
+ this.running = false;
260
+ if (this.timer !== undefined) {
261
+ clearTimeout(this.timer);
262
+ this.timer = undefined;
263
+ }
264
+ }
265
+ scheduleNext() {
266
+ try {
267
+ if (!this.running || !this.options.collector.available) {
268
+ return;
269
+ }
270
+ const remainingNs = this.nextDeadlineNs - this.nowNs();
271
+ const delayMs = safeTimeoutDelayMs(remainingNs);
272
+ this.timer = setTimeout(() => this.onTimer(), delayMs);
273
+ if (typeof this.timer.unref === "function") {
274
+ this.timer.unref();
275
+ }
276
+ }
277
+ catch (error) {
278
+ this.fail(error);
279
+ }
280
+ }
281
+ onTimer() {
282
+ try {
283
+ this.timer = undefined;
284
+ if (!this.running || !this.options.collector.available) {
285
+ return;
286
+ }
287
+ const nowNs = this.nowNs();
288
+ if (nowNs < this.nextDeadlineNs) {
289
+ this.scheduleNext();
290
+ return;
291
+ }
292
+ this.options.collector.capture(this.options.snapshotFactory);
293
+ if (!this.options.collector.available) {
294
+ this.running = false;
295
+ return;
296
+ }
297
+ do {
298
+ this.nextDeadlineNs += this.cadenceNs;
299
+ } while (this.nextDeadlineNs <= nowNs);
300
+ this.scheduleNext();
301
+ }
302
+ catch (error) {
303
+ this.fail(error);
304
+ }
305
+ }
306
+ fail(error) {
307
+ this.stopWithoutFinalizing();
308
+ this.options.collector.disable("capture_failure");
309
+ try {
310
+ this.options.onFailure?.("capture_failure", sanitizedExceptionClassChain(error));
311
+ }
312
+ catch {
313
+ // Logging a report-only warning is best-effort.
314
+ }
315
+ }
316
+ }
317
+ exports.ReportHistoryWorker = ReportHistoryWorker;
318
+ function sanitizedExceptionClassChain(error) {
319
+ const classes = [];
320
+ const seen = new Set();
321
+ let current = error;
322
+ while (current !== undefined && current !== null && classes.length < 8 && !seen.has(current)) {
323
+ seen.add(current);
324
+ const rawName = typeof current === "object"
325
+ ? String(current.constructor?.name ?? "Error")
326
+ : typeof current;
327
+ const safeName = rawName.replace(/[^A-Za-z0-9_.$-]/g, "_").slice(0, 64) || "Error";
328
+ classes.push(safeName);
329
+ current = typeof current === "object"
330
+ ? current.cause
331
+ : undefined;
332
+ }
333
+ return classes.join(" -> ") || "Error";
334
+ }
335
+ function cadenceNanoseconds(seconds) {
336
+ if (!Number.isFinite(seconds) || seconds <= 0) {
337
+ throw new RangeError("Invalid report history cadence.");
338
+ }
339
+ const safelyRepresentableSeconds = Math.min(seconds, Number.MAX_SAFE_INTEGER);
340
+ const wholeSeconds = Math.trunc(safelyRepresentableSeconds);
341
+ const fractionalNanoseconds = Math.round(Math.max(0, safelyRepresentableSeconds - wholeSeconds) * 1000000000);
342
+ const cadence = BigInt(wholeSeconds) * 1000000000n + BigInt(fractionalNanoseconds);
343
+ if (cadence <= 0n) {
344
+ throw new RangeError("Report history cadence is below one nanosecond.");
345
+ }
346
+ return cadence;
347
+ }
348
+ function safeTimeoutDelayMs(remainingNs) {
349
+ if (remainingNs <= 0n) {
350
+ return 1;
351
+ }
352
+ const milliseconds = (remainingNs + 999999n) / 1000000n;
353
+ return Number(milliseconds > 2147483647n ? 2147483647n : milliseconds);
354
+ }
355
+ function deriveScenarioRates(points) {
356
+ const scenarioNames = [...new Set(points.flatMap((point) => point.scenarios.map((scenario) => scenario.scenarioName)))];
357
+ return scenarioNames.map((scenarioName) => {
358
+ let previousElapsed = 0;
359
+ let previousCount = 0;
360
+ return {
361
+ scenarioName,
362
+ values: points.map((point) => {
363
+ const scenario = point.scenarios.find((candidate) => candidate.scenarioName === scenarioName);
364
+ const currentCount = scenario?.all?.count
365
+ ?? ((scenario?.ok?.count ?? 0) + (scenario?.failed?.count ?? 0));
366
+ const duration = point.elapsedSeconds - previousElapsed;
367
+ const rate = duration > 0
368
+ ? Math.max(0, currentCount - previousCount) / duration
369
+ : null;
370
+ previousElapsed = point.elapsedSeconds;
371
+ previousCount = currentCount;
372
+ return rate === null || !Number.isFinite(rate) ? null : rate;
373
+ })
374
+ };
375
+ });
376
+ }
377
+ function projectScenario(source) {
378
+ return {
379
+ scenarioName: String(source.scenarioName ?? ""),
380
+ sortIndex: finiteNonNegativeInteger(source.sortIndex),
381
+ ...(source.all ? { all: projectMeasurement(source.all) } : {}),
382
+ ...(source.ok ? { ok: projectMeasurement(source.ok) } : {}),
383
+ ...(source.failed ? { failed: projectMeasurement(source.failed) } : {})
384
+ };
385
+ }
386
+ function projectMeasurement(source) {
387
+ return {
388
+ count: finiteNonNegativeInteger(source.count) ?? 0,
389
+ bytes: finiteNonNegative(source.bytes) ?? 0,
390
+ approximate: source.approximate === true,
391
+ ...optionalFinite("percent50Ms", source.percent50Ms),
392
+ ...optionalFinite("percent75Ms", source.percent75Ms),
393
+ ...optionalFinite("percent95Ms", source.percent95Ms),
394
+ ...optionalFinite("percent99Ms", source.percent99Ms)
395
+ };
396
+ }
397
+ function optionalFinite(key, value) {
398
+ const normalized = finiteNonNegative(value);
399
+ return normalized === undefined ? {} : { [key]: normalized };
400
+ }
401
+ function finiteNonNegative(value) {
402
+ return typeof value === "number" && Number.isFinite(value) && value >= 0
403
+ ? value
404
+ : undefined;
405
+ }
406
+ function finiteNonNegativeInteger(value) {
407
+ const normalized = finiteNonNegative(value);
408
+ return normalized === undefined ? undefined : Math.trunc(normalized);
409
+ }
410
+ function clonePoint(point) {
411
+ return {
412
+ elapsedSeconds: point.elapsedSeconds,
413
+ terminal: point.terminal,
414
+ scenarios: point.scenarios.map((scenario) => ({
415
+ ...scenario,
416
+ ...(scenario.all ? { all: { ...scenario.all } } : {}),
417
+ ...(scenario.ok ? { ok: { ...scenario.ok } } : {}),
418
+ ...(scenario.failed ? { failed: { ...scenario.failed } } : {})
419
+ }))
420
+ };
421
+ }