@loadstrike/loadstrike-sdk 1.0.26701 → 1.0.27101

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/README.md CHANGED
@@ -8,6 +8,7 @@ LoadStrike is a developer-first load testing SDK for Node.js applications, servi
8
8
  - Generate safe starter scenarios from captured HAR, OpenTelemetry trace JSON, browser recordings, or message pairs with Trace-to-test Autopilot.
9
9
  - Exercise HTTP and event-driven workflows across multiple steps.
10
10
  - Apply load simulations, thresholds, and custom metrics to real transactions.
11
+ - Split a single load profile across weighted scenario mixes.
11
12
  - Generate local reports and, on Business and Enterprise, publish observability data to supported sinks.
12
13
 
13
14
  Built-in transport coverage includes HTTP, Kafka, RabbitMQ, NATS, Redis Streams, Azure Event Hubs, Push Diffusion, and delegate-based custom streams. Local report output supports HTML, Markdown, TXT, and CSV, and Business and Enterprise can publish to InfluxDB, TimescaleDB, Grafana Loki, Datadog, Splunk HEC, and OpenTelemetry Collector.
@@ -53,6 +54,12 @@ const result = await LoadStrikeRunner
53
54
 
54
55
  `run()` returns the detailed run result, including generated report files, scenario statistics, metrics, and sink status.
55
56
 
57
+ ## Traffic Mixes
58
+
59
+ Use `LoadStrikeTrafficMix` on Business and Enterprise plans when one total load profile should be distributed across multiple scenario lanes. For example, a 1000 requests-per-second profile with scenario weights of 60, 30, and 10 sends roughly 600 requests per second to the first scenario, 300 to the second, and 100 to the third.
60
+
61
+ Each lane is still a normal scenario with its own named steps, thresholds, reports, and portal results. Register the mix with `LoadStrikeRunner.registerTrafficMix(...)` or add it to a runner with `.addTrafficMix(...)`.
62
+
56
63
  ## Trace-To-Test Autopilot
57
64
 
58
65
  Use `await LoadStrikeAutopilot.generate(...)` to infer a starter plan from a captured artifact. Set `Options.RunnerKey` so generation can validate the Trace-To-Test Autopilot entitlement. Check `result.Readiness` and `result.ReadinessFailures` first; call `result.buildScenario()` only when it is `LoadStrikeAutopilotReadiness.Ready`, then execute the scenario through the normal runner with a valid `RunnerKey`.
package/dist/cjs/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InfluxDbReportingSink = exports.GrafanaLokiReportingSinkOptions = exports.GrafanaLokiReportingSink = exports.DatadogReportingSinkOptions = exports.DatadogReportingSink = exports.HttpAuthOptions = exports.HttpOAuth2ClientCredentialsOptions = exports.PushDiffusionEndpointDefinition = exports.DelegateStreamEndpointDefinition = exports.SqsEndpointDefinition = exports.AzureEventHubsEndpointDefinition = exports.RedisStreamsEndpointDefinition = exports.NatsEndpointDefinition = exports.RabbitMqEndpointDefinition = exports.KafkaSaslOptions = exports.KafkaEndpointDefinition = exports.HttpEndpointDefinition = exports.TrafficEndpointDefinition = exports.LOADSTRIKE_TRACE_ID_TRACKING_FIELD = exports.LOADSTRIKE_TRACE_ID_HEADER = exports.TrackingFieldSelector = exports.TrackingPayloadBuilder = exports.RedisCorrelationStore = exports.RedisCorrelationStoreOptions = exports.InMemoryCorrelationStore = exports.CrossPlatformTrackingRuntime = exports.CorrelationStoreConfiguration = exports.LoadStrikeThreshold = exports.LoadStrikeStep = exports.LoadStrikeGauge = exports.LoadStrikeCounter = exports.LoadStrikeMetric = exports.CrossPlatformTrackingConfiguration = exports.LoadStrikeSimulation = exports.LoadStrikeScenario = exports.LoadStrikeRunner = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeResponse = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = exports.LoadStrikePluginDataTable = exports.LoadStrikePluginData = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeAutopilotReadiness = exports.LoadStrikeAutopilotResult = exports.LoadStrikeAutopilot = void 0;
4
- exports.TimescaleDbReportingSinkOptions = exports.TimescaleDbReportingSink = exports.SplunkReportingSinkOptions = exports.SplunkReportingSink = exports.PortalReportingSink = exports.OtelCollectorReportingSinkOptions = exports.OtelCollectorReportingSink = exports.InfluxDbReportingSinkOptions = void 0;
3
+ exports.HttpOAuth2ClientCredentialsOptions = exports.WebSocketEndpointDefinition = exports.GrpcEndpointDefinition = exports.PushDiffusionEndpointDefinition = exports.DelegateStreamEndpointDefinition = exports.SqsEndpointDefinition = exports.AzureEventHubsEndpointDefinition = exports.RedisStreamsEndpointDefinition = exports.NatsEndpointDefinition = exports.RabbitMqEndpointDefinition = exports.KafkaSaslOptions = exports.KafkaEndpointDefinition = exports.HttpEndpointDefinition = exports.TrafficEndpointDefinition = exports.LOADSTRIKE_TRACE_ID_TRACKING_FIELD = exports.LOADSTRIKE_TRACE_ID_HEADER = exports.TrackingFieldSelector = exports.TrackingPayloadBuilder = exports.RedisCorrelationStore = exports.RedisCorrelationStoreOptions = exports.InMemoryCorrelationStore = exports.CrossPlatformTrackingRuntime = exports.CorrelationStoreConfiguration = exports.LoadStrikeThreshold = exports.LoadStrikeStep = exports.LoadStrikeGauge = exports.LoadStrikeCounter = exports.LoadStrikeMetric = exports.CrossPlatformTrackingConfiguration = exports.LoadStrikeTrafficMix = exports.LoadStrikeSimulation = exports.LoadStrikeScenarioShare = exports.LoadStrikeScenario = exports.LoadStrikeRunner = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeResponse = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = exports.LoadStrikePluginDataTable = exports.LoadStrikePluginData = exports.LoadStrikeBrowserWebVitals = exports.LoadStrikeAccessibility = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeAutopilotReadiness = exports.LoadStrikeAutopilotResult = exports.LoadStrikeAutopilot = void 0;
4
+ exports.TimescaleDbReportingSinkOptions = exports.TimescaleDbReportingSink = exports.SplunkReportingSinkOptions = exports.SplunkReportingSink = exports.PortalReportingSink = exports.OtelCollectorReportingSinkOptions = exports.OtelCollectorReportingSink = exports.InfluxDbReportingSinkOptions = exports.InfluxDbReportingSink = exports.GrafanaLokiReportingSinkOptions = exports.GrafanaLokiReportingSink = exports.DatadogReportingSinkOptions = exports.DatadogReportingSink = exports.HttpAuthOptions = void 0;
5
5
  var autopilot_js_1 = require("./autopilot.js");
6
6
  Object.defineProperty(exports, "LoadStrikeAutopilot", { enumerable: true, get: function () { return autopilot_js_1.LoadStrikeAutopilot; } });
7
7
  Object.defineProperty(exports, "LoadStrikeAutopilotResult", { enumerable: true, get: function () { return autopilot_js_1.LoadStrikeAutopilotResult; } });
@@ -11,6 +11,8 @@ var runtime_js_1 = require("./runtime.js");
11
11
  Object.defineProperty(exports, "CrossPlatformScenarioConfigurator", { enumerable: true, get: function () { return runtime_js_1.CrossPlatformScenarioConfigurator; } });
12
12
  Object.defineProperty(exports, "ScenarioTrackingExtensions", { enumerable: true, get: function () { return runtime_js_1.ScenarioTrackingExtensions; } });
13
13
  Object.defineProperty(exports, "LoadStrikeContext", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeContext; } });
14
+ Object.defineProperty(exports, "LoadStrikeAccessibility", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeAccessibility; } });
15
+ Object.defineProperty(exports, "LoadStrikeBrowserWebVitals", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeBrowserWebVitals; } });
14
16
  Object.defineProperty(exports, "LoadStrikePluginData", { enumerable: true, get: function () { return runtime_js_1.LoadStrikePluginData; } });
15
17
  Object.defineProperty(exports, "LoadStrikePluginDataTable", { enumerable: true, get: function () { return runtime_js_1.LoadStrikePluginDataTable; } });
16
18
  Object.defineProperty(exports, "LoadStrikeNodeType", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeNodeType; } });
@@ -21,7 +23,9 @@ Object.defineProperty(exports, "LoadStrikeScenarioOperation", { enumerable: true
21
23
  Object.defineProperty(exports, "LoadStrikeOperationType", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeOperationType; } });
22
24
  Object.defineProperty(exports, "LoadStrikeRunner", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeRunner; } });
23
25
  Object.defineProperty(exports, "LoadStrikeScenario", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeScenario; } });
26
+ Object.defineProperty(exports, "LoadStrikeScenarioShare", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeScenarioShare; } });
24
27
  Object.defineProperty(exports, "LoadStrikeSimulation", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeSimulation; } });
28
+ Object.defineProperty(exports, "LoadStrikeTrafficMix", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeTrafficMix; } });
25
29
  Object.defineProperty(exports, "CrossPlatformTrackingConfiguration", { enumerable: true, get: function () { return runtime_js_1.CrossPlatformTrackingConfiguration; } });
26
30
  Object.defineProperty(exports, "LoadStrikeMetric", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeMetric; } });
27
31
  Object.defineProperty(exports, "LoadStrikeCounter", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeCounter; } });
@@ -50,6 +54,8 @@ Object.defineProperty(exports, "AzureEventHubsEndpointDefinition", { enumerable:
50
54
  Object.defineProperty(exports, "SqsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.SqsEndpointDefinition; } });
51
55
  Object.defineProperty(exports, "DelegateStreamEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.DelegateStreamEndpointDefinition; } });
52
56
  Object.defineProperty(exports, "PushDiffusionEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.PushDiffusionEndpointDefinition; } });
57
+ Object.defineProperty(exports, "GrpcEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.GrpcEndpointDefinition; } });
58
+ Object.defineProperty(exports, "WebSocketEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.WebSocketEndpointDefinition; } });
53
59
  Object.defineProperty(exports, "HttpOAuth2ClientCredentialsOptions", { enumerable: true, get: function () { return transports_js_1.HttpOAuth2ClientCredentialsOptions; } });
54
60
  Object.defineProperty(exports, "HttpAuthOptions", { enumerable: true, get: function () { return transports_js_1.HttpAuthOptions; } });
55
61
  var sinks_js_1 = require("./sinks.js");
package/dist/cjs/local.js CHANGED
@@ -32,9 +32,21 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
36
+ if (kind === "m") throw new TypeError("Private method is not writable");
37
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
38
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
39
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
40
+ };
41
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
42
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
43
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
44
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
45
+ };
35
46
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
47
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
48
  };
49
+ var _LoadStrikeLocalClient_licensingApiBaseUrl, _LoadStrikeLocalClient_signingKeyCache;
38
50
  Object.defineProperty(exports, "__esModule", { value: true });
39
51
  exports.__loadstrikeTestExports = exports.LoadStrikeLocalClient = void 0;
40
52
  const node_os_1 = __importDefault(require("node:os"));
@@ -67,7 +79,9 @@ const TRACKING_FEATURE_BY_KIND = {
67
79
  delegatestream: "endpoint.delegate_stream",
68
80
  nats: "endpoint.nats",
69
81
  redisstreams: "endpoint.redis_streams",
70
- sqs: "endpoint.sqs"
82
+ sqs: "endpoint.sqs",
83
+ grpc: "endpoint.grpc",
84
+ websocket: "endpoint.websocket"
71
85
  };
72
86
  const CI_ENVIRONMENT_VARIABLES = [
73
87
  "GITHUB_ACTIONS",
@@ -92,13 +106,14 @@ class LoadStrikeLocalClient {
92
106
  * Use this when the surrounding wrapper type makes this operation the clearest way to express your intent.
93
107
  */
94
108
  constructor(options = {}) {
95
- this.signingKeyCache = new Map();
109
+ _LoadStrikeLocalClient_licensingApiBaseUrl.set(this, void 0);
110
+ _LoadStrikeLocalClient_signingKeyCache.set(this, new Map());
96
111
  assertNoDisableLicenseEnforcementOption(options, "LoadStrikeLocalClient");
97
- this.licensingApiBaseUrl = resolveLicensingApiBaseUrl();
112
+ __classPrivateFieldSet(this, _LoadStrikeLocalClient_licensingApiBaseUrl, resolveLicensingApiBaseUrl(), "f");
98
113
  this.licenseValidationTimeoutMs = normalizeTimeoutMs(options.licenseValidationTimeoutMs);
99
114
  }
100
115
  portalReportingIngestUrl() {
101
- return `${this.licensingApiBaseUrl.replace(/\/+$/, "")}/api/v1/reporting/ingest`;
116
+ return `${__classPrivateFieldGet(this, _LoadStrikeLocalClient_licensingApiBaseUrl, "f").replace(/\/+$/, "")}/api/v1/reporting/ingest`;
102
117
  }
103
118
  async run(request) {
104
119
  const sanitized = sanitizeRequest(request);
@@ -367,8 +382,8 @@ class LoadStrikeLocalClient {
367
382
  const nowMs = Date.now();
368
383
  const normalizedKeyId = stringOrDefault(keyId, "default").trim() || "default";
369
384
  const normalizedAlgorithm = stringOrDefault(algorithm, "RS256").toUpperCase();
370
- const cacheKey = `${this.licensingApiBaseUrl.toLowerCase()}|${normalizedAlgorithm}:${normalizedKeyId}`;
371
- const cached = this.signingKeyCache.get(cacheKey);
385
+ const cacheKey = `${__classPrivateFieldGet(this, _LoadStrikeLocalClient_licensingApiBaseUrl, "f").toLowerCase()}|${normalizedAlgorithm}:${normalizedKeyId}`;
386
+ const cached = __classPrivateFieldGet(this, _LoadStrikeLocalClient_signingKeyCache, "f").get(cacheKey);
372
387
  if (cached && cached.expiresAtUtc > nowMs) {
373
388
  return cached;
374
389
  }
@@ -396,7 +411,7 @@ class LoadStrikeLocalClient {
396
411
  publicKeyPem,
397
412
  expiresAtUtc: nowMs + (15 * 60 * 1000)
398
413
  };
399
- this.signingKeyCache.set(cacheKey, keyRecord);
414
+ __classPrivateFieldGet(this, _LoadStrikeLocalClient_signingKeyCache, "f").set(cacheKey, keyRecord);
400
415
  return keyRecord;
401
416
  }
402
417
  finally {
@@ -448,7 +463,7 @@ class LoadStrikeLocalClient {
448
463
  }
449
464
  }
450
465
  async postLicensingRequest(path, payload, signal) {
451
- const response = await fetch(`${this.licensingApiBaseUrl.replace(/\/+$/, "")}${path}`, {
466
+ const response = await fetch(`${__classPrivateFieldGet(this, _LoadStrikeLocalClient_licensingApiBaseUrl, "f").replace(/\/+$/, "")}${path}`, {
452
467
  method: "POST",
453
468
  headers: {
454
469
  "Content-Type": "application/json",
@@ -467,7 +482,7 @@ class LoadStrikeLocalClient {
467
482
  return { response, json };
468
483
  }
469
484
  async getLicensingRequest(path, signal) {
470
- const response = await fetch(`${this.licensingApiBaseUrl.replace(/\/+$/, "")}${path}`, {
485
+ const response = await fetch(`${__classPrivateFieldGet(this, _LoadStrikeLocalClient_licensingApiBaseUrl, "f").replace(/\/+$/, "")}${path}`, {
471
486
  method: "GET",
472
487
  headers: {
473
488
  Accept: "application/json"
@@ -485,6 +500,7 @@ class LoadStrikeLocalClient {
485
500
  }
486
501
  }
487
502
  exports.LoadStrikeLocalClient = LoadStrikeLocalClient;
503
+ _LoadStrikeLocalClient_licensingApiBaseUrl = new WeakMap(), _LoadStrikeLocalClient_signingKeyCache = new WeakMap();
488
504
  function assertNoDisableLicenseEnforcementOption(value, source) {
489
505
  if (value == null || typeof value !== "object" || Array.isArray(value)) {
490
506
  return;
@@ -514,7 +530,43 @@ function resolveLicensingApiBaseUrl() {
514
530
  return normalizeLicensingApiBaseUrl(developmentLicensingApiBaseUrlOverride);
515
531
  }
516
532
  function setDevelopmentLicensingApiBaseUrlOverride(value) {
517
- developmentLicensingApiBaseUrlOverride = value;
533
+ if (value != null && value.trim()) {
534
+ assertInternalTestHarnessLicensingOverride();
535
+ const normalized = normalizeLicensingApiBaseUrl(value);
536
+ if (!isLoopbackHttpBaseUrl(normalized)) {
537
+ throw new TypeError("Internal development licensing API overrides must use a loopback http URL.");
538
+ }
539
+ developmentLicensingApiBaseUrlOverride = normalized;
540
+ return;
541
+ }
542
+ if (value != null) {
543
+ assertInternalTestHarnessLicensingOverride();
544
+ }
545
+ developmentLicensingApiBaseUrlOverride = undefined;
546
+ }
547
+ function assertInternalTestHarnessLicensingOverride() {
548
+ if (isInternalTestHarnessCall()) {
549
+ return;
550
+ }
551
+ throw new TypeError("Internal development licensing API override is available only to LoadStrike SDK tests.");
552
+ }
553
+ function isInternalTestHarnessCall() {
554
+ const stack = String(new Error().stack ?? "").replace(/\\/g, "/").toLowerCase();
555
+ return stack.includes("/sdk/ts/tests/");
556
+ }
557
+ function isLoopbackHttpBaseUrl(value) {
558
+ let parsed;
559
+ try {
560
+ parsed = new URL(value);
561
+ }
562
+ catch {
563
+ return false;
564
+ }
565
+ if (parsed.protocol !== "http:") {
566
+ return false;
567
+ }
568
+ const host = parsed.hostname.trim().toLowerCase();
569
+ return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1" || host.endsWith(".localhost");
518
570
  }
519
571
  function normalizeTimeoutMs(value) {
520
572
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.__loadstrikeTestExports = exports.LoadStrikeRunner = exports.LoadStrikeScenario = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeThreshold = exports.LoadStrikeMetric = exports.LoadStrikeGauge = exports.LoadStrikeCounter = exports.LoadStrikeSimulation = exports.LoadStrikeStep = exports.LoadStrikeResponse = exports.LoadStrikePluginData = exports.LoadStrikePluginDataTable = exports.CrossPlatformTrackingConfiguration = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = void 0;
6
+ exports.__loadstrikeTestExports = exports.LoadStrikeRunner = exports.LoadStrikeTrafficMix = exports.LoadStrikeScenarioShare = exports.LoadStrikeScenario = exports.LoadStrikeBrowserWebVitals = exports.LoadStrikeAccessibility = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeThreshold = exports.LoadStrikeMetric = exports.LoadStrikeGauge = exports.LoadStrikeCounter = exports.LoadStrikeSimulation = exports.LoadStrikeStep = exports.LoadStrikeResponse = exports.LoadStrikePluginData = exports.LoadStrikePluginDataTable = exports.CrossPlatformTrackingConfiguration = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = void 0;
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_crypto_1 = require("node:crypto");
9
9
  const node_os_1 = __importDefault(require("node:os"));
@@ -75,6 +75,7 @@ exports.CrossPlatformTrackingConfiguration = {
75
75
  return this.forDuration(configuration, durationSeconds, cancellationSignal);
76
76
  }
77
77
  };
78
+ const TRAFFIC_MIX_FEATURE = "workload.traffic_mix";
78
79
  class LoadStrikePluginDataTable {
79
80
  /**
80
81
  * Exposes the public constructor operation.
@@ -1621,6 +1622,114 @@ class LoadStrikeContext {
1621
1622
  }
1622
1623
  }
1623
1624
  exports.LoadStrikeContext = LoadStrikeContext;
1625
+ const ACCESSIBILITY_TESTING_FEATURE = "testing.accessibility";
1626
+ const BROWSER_WEB_VITALS_FEATURE = "testing.browser_web_vitals";
1627
+ class LoadStrikeAccessibility {
1628
+ static createScenario(name, options, check) {
1629
+ if (typeof name !== "string" || !name.trim()) {
1630
+ throw new Error("Scenario name must be provided.");
1631
+ }
1632
+ validateAbsoluteUrl(options?.url, "Accessibility check URL");
1633
+ if (typeof check !== "function") {
1634
+ throw new TypeError("Accessibility check callback must be provided.");
1635
+ }
1636
+ return LoadStrikeScenario
1637
+ .create(name, async (scenarioContext) => {
1638
+ const result = await check({ options, scenarioContext });
1639
+ const failure = evaluateAccessibilityResult(options, result);
1640
+ return failure
1641
+ ? LoadStrikeResponse.fail("accessibility", failure)
1642
+ : LoadStrikeResponse.ok("200", 0, `Accessibility check passed with ${(result.violations ?? []).length} violation(s).`);
1643
+ })
1644
+ .withoutWarmUp()
1645
+ .__loadStrikeWithInternalLicenseFeatures(ACCESSIBILITY_TESTING_FEATURE);
1646
+ }
1647
+ static CreateScenario(name, options, check) {
1648
+ return LoadStrikeAccessibility.createScenario(name, options, check);
1649
+ }
1650
+ }
1651
+ exports.LoadStrikeAccessibility = LoadStrikeAccessibility;
1652
+ class LoadStrikeBrowserWebVitals {
1653
+ static createScenario(name, options, measure) {
1654
+ if (typeof name !== "string" || !name.trim()) {
1655
+ throw new Error("Scenario name must be provided.");
1656
+ }
1657
+ validateAbsoluteUrl(options?.url, "Browser Web Vitals URL");
1658
+ if (typeof measure !== "function") {
1659
+ throw new TypeError("Browser Web Vitals measurement callback must be provided.");
1660
+ }
1661
+ return LoadStrikeScenario
1662
+ .create(name, async (scenarioContext) => {
1663
+ const result = await measure({ options, scenarioContext });
1664
+ const failure = evaluateWebVitalsResult(options, result);
1665
+ return failure
1666
+ ? LoadStrikeResponse.fail("web_vitals", failure)
1667
+ : LoadStrikeResponse.ok("200", 0, "Browser Web Vitals check passed.");
1668
+ })
1669
+ .withoutWarmUp()
1670
+ .__loadStrikeWithInternalLicenseFeatures(BROWSER_WEB_VITALS_FEATURE);
1671
+ }
1672
+ static CreateScenario(name, options, measure) {
1673
+ return LoadStrikeBrowserWebVitals.createScenario(name, options, measure);
1674
+ }
1675
+ }
1676
+ exports.LoadStrikeBrowserWebVitals = LoadStrikeBrowserWebVitals;
1677
+ function validateAbsoluteUrl(value, label) {
1678
+ if (typeof value !== "string" || !value.trim()) {
1679
+ throw new Error(`${label} must be provided.`);
1680
+ }
1681
+ try {
1682
+ const parsed = new URL(value);
1683
+ if (!parsed.protocol || !parsed.host) {
1684
+ throw new Error("invalid");
1685
+ }
1686
+ }
1687
+ catch {
1688
+ throw new Error(`${label} must be an absolute URL.`);
1689
+ }
1690
+ }
1691
+ function evaluateAccessibilityResult(options, result) {
1692
+ if (!result) {
1693
+ return "Accessibility check did not return a result.";
1694
+ }
1695
+ const violations = Array.isArray(result.violations) ? result.violations : [];
1696
+ const maxViolations = Math.max(0, Number(options.maxViolations ?? 0));
1697
+ const critical = countAccessibilityImpact(violations, "critical");
1698
+ const serious = countAccessibilityImpact(violations, "serious");
1699
+ const maxCritical = Math.max(0, Number(options.maxCriticalViolations ?? 0));
1700
+ const maxSerious = Math.max(0, Number(options.maxSeriousViolations ?? 0));
1701
+ if (violations.length > maxViolations) {
1702
+ return `Accessibility violations ${violations.length} exceeded limit ${maxViolations}.`;
1703
+ }
1704
+ if (critical > maxCritical) {
1705
+ return `Critical accessibility violations ${critical} exceeded limit ${maxCritical}.`;
1706
+ }
1707
+ if (serious > maxSerious) {
1708
+ return `Serious accessibility violations ${serious} exceeded limit ${maxSerious}.`;
1709
+ }
1710
+ return "";
1711
+ }
1712
+ function countAccessibilityImpact(violations, impact) {
1713
+ return violations.filter((violation) => String(violation.impact ?? "").toLowerCase() === impact).length;
1714
+ }
1715
+ function evaluateWebVitalsResult(options, result) {
1716
+ if (!result) {
1717
+ return "Browser Web Vitals check did not return a result.";
1718
+ }
1719
+ return firstWebVitalViolation(["LCP", result.largestContentfulPaintMs, options.maxLargestContentfulPaintMs, "ms"], ["INP", result.interactionToNextPaintMs, options.maxInteractionToNextPaintMs, "ms"], ["CLS", result.cumulativeLayoutShift, options.maxCumulativeLayoutShift, ""], ["FCP", result.firstContentfulPaintMs, options.maxFirstContentfulPaintMs, "ms"], ["TTFB", result.timeToFirstByteMs, options.maxTimeToFirstByteMs, "ms"]);
1720
+ }
1721
+ function firstWebVitalViolation(...values) {
1722
+ for (const [name, actualRaw, limitRaw, unit] of values) {
1723
+ const actual = Number(actualRaw);
1724
+ const limit = Number(limitRaw);
1725
+ if (!Number.isFinite(actual) || !Number.isFinite(limit) || actual <= limit) {
1726
+ continue;
1727
+ }
1728
+ const suffix = unit ? ` ${unit}` : "";
1729
+ return `${name} ${actual}${suffix} exceeded limit ${limit}${suffix}.`;
1730
+ }
1731
+ return "";
1732
+ }
1624
1733
  class LoadStrikeScenario {
1625
1734
  constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail, internalLicenseFeatures = []) {
1626
1735
  this.name = name;
@@ -1967,6 +2076,105 @@ class LoadStrikeScenario {
1967
2076
  }
1968
2077
  }
1969
2078
  exports.LoadStrikeScenario = LoadStrikeScenario;
2079
+ class LoadStrikeScenarioShare {
2080
+ constructor(scenario, weight) {
2081
+ this.scenario = scenario;
2082
+ this.weight = weight;
2083
+ }
2084
+ /**
2085
+ * Creates a weighted scenario lane for a traffic mix.
2086
+ * Use this when a total workload should be distributed across several scenarios.
2087
+ */
2088
+ static create(scenario, weight) {
2089
+ if (!(scenario instanceof LoadStrikeScenario)) {
2090
+ throw new TypeError("Scenario must be provided.");
2091
+ }
2092
+ const normalizedWeight = Math.trunc(weight);
2093
+ if (!Number.isFinite(weight) || normalizedWeight <= 0) {
2094
+ throw new RangeError("Scenario share weight should be greater than zero.");
2095
+ }
2096
+ return new LoadStrikeScenarioShare(scenario, normalizedWeight);
2097
+ }
2098
+ /**
2099
+ * Creates a weighted scenario lane for a traffic mix.
2100
+ * Use this when a total workload should be distributed across several scenarios.
2101
+ */
2102
+ static Create(scenario, weight) {
2103
+ return LoadStrikeScenarioShare.create(scenario, weight);
2104
+ }
2105
+ }
2106
+ exports.LoadStrikeScenarioShare = LoadStrikeScenarioShare;
2107
+ class LoadStrikeTrafficMix {
2108
+ constructor(name, totalLoad = [], scenarioMix = []) {
2109
+ this.name = name;
2110
+ this.totalLoad = totalLoad.map((simulation) => attachLoadSimulationProjection({ ...simulation }));
2111
+ this.scenarioMix = [...scenarioMix];
2112
+ }
2113
+ /**
2114
+ * Creates a traffic mix definition.
2115
+ * Use this when one total load profile should be split across multiple scenario lanes.
2116
+ */
2117
+ static create(name) {
2118
+ return new LoadStrikeTrafficMix(requireNonEmpty(name, "Traffic mix name must be provided."));
2119
+ }
2120
+ /**
2121
+ * Creates a traffic mix definition.
2122
+ * Use this when one total load profile should be split across multiple scenario lanes.
2123
+ */
2124
+ static Create(name) {
2125
+ return LoadStrikeTrafficMix.create(name);
2126
+ }
2127
+ /**
2128
+ * Sets the total workload shape for the mix.
2129
+ * Use this when the same high-level load profile should be distributed by scenario weights.
2130
+ */
2131
+ withTotalLoad(...totalLoad) {
2132
+ if (!totalLoad.length) {
2133
+ throw new Error("At least one total load simulation should be provided.");
2134
+ }
2135
+ return new LoadStrikeTrafficMix(this.name, totalLoad, this.scenarioMix);
2136
+ }
2137
+ /**
2138
+ * Sets the total workload shape for the mix.
2139
+ * Use this when the same high-level load profile should be distributed by scenario weights.
2140
+ */
2141
+ WithTotalLoad(...totalLoad) {
2142
+ return this.withTotalLoad(...totalLoad);
2143
+ }
2144
+ /**
2145
+ * Sets the weighted scenario lanes for the mix.
2146
+ * Use this when each operation should receive a fixed proportion of the total workload.
2147
+ */
2148
+ withScenarioMix(...scenarioMix) {
2149
+ if (!scenarioMix.length) {
2150
+ throw new Error("At least one scenario share should be provided.");
2151
+ }
2152
+ if (scenarioMix.some((share) => !(share instanceof LoadStrikeScenarioShare))) {
2153
+ throw new TypeError("Scenario share collection cannot contain null values.");
2154
+ }
2155
+ return new LoadStrikeTrafficMix(this.name, this.totalLoad, scenarioMix);
2156
+ }
2157
+ /**
2158
+ * Sets the weighted scenario lanes for the mix.
2159
+ * Use this when each operation should receive a fixed proportion of the total workload.
2160
+ */
2161
+ WithScenarioMix(...scenarioMix) {
2162
+ return this.withScenarioMix(...scenarioMix);
2163
+ }
2164
+ expandScenarios() {
2165
+ return expandTrafficMixScenarios(this);
2166
+ }
2167
+ ExpandScenarios() {
2168
+ return this.expandScenarios();
2169
+ }
2170
+ __loadStrikeTotalLoad() {
2171
+ return this.totalLoad.map((simulation) => attachLoadSimulationProjection({ ...simulation }));
2172
+ }
2173
+ __loadStrikeScenarioMix() {
2174
+ return [...this.scenarioMix];
2175
+ }
2176
+ }
2177
+ exports.LoadStrikeTrafficMix = LoadStrikeTrafficMix;
1970
2178
  class LoadStrikeRunner {
1971
2179
  constructor(scenarios, options, contextConfigurators = []) {
1972
2180
  this.scenarios = scenarios;
@@ -2002,6 +2210,20 @@ class LoadStrikeRunner {
2002
2210
  static RegisterScenarios(...scenarios) {
2003
2211
  return LoadStrikeRunner.registerScenarios(...scenarios);
2004
2212
  }
2213
+ /**
2214
+ * Registers a traffic mix on a fresh runnable context.
2215
+ * Use this when one total load profile should be split across weighted scenario lanes.
2216
+ */
2217
+ static registerTrafficMix(trafficMix) {
2218
+ return LoadStrikeRunner.registerScenarios(...expandTrafficMixScenarios(trafficMix));
2219
+ }
2220
+ /**
2221
+ * Registers a traffic mix on a fresh runnable context.
2222
+ * Use this when one total load profile should be split across weighted scenario lanes.
2223
+ */
2224
+ static RegisterTrafficMix(trafficMix) {
2225
+ return LoadStrikeRunner.registerTrafficMix(trafficMix);
2226
+ }
2005
2227
  /**
2006
2228
  * Toggles realtime console metric output.
2007
2229
  * Use this when a local run or CI log should stream live throughput and latency updates.
@@ -2256,6 +2478,21 @@ class LoadStrikeRunner {
2256
2478
  AddScenarios(...scenarios) {
2257
2479
  return this.addScenarios(...scenarios);
2258
2480
  }
2481
+ /**
2482
+ * Adds a traffic mix to the current runner.
2483
+ * Use this when one total load profile should be split across weighted scenario lanes.
2484
+ */
2485
+ addTrafficMix(trafficMix) {
2486
+ this.scenarios = [...this.scenarios, ...expandTrafficMixScenarios(trafficMix)];
2487
+ return this;
2488
+ }
2489
+ /**
2490
+ * Adds a traffic mix to the current runner.
2491
+ * Use this when one total load profile should be split across weighted scenario lanes.
2492
+ */
2493
+ AddTrafficMix(trafficMix) {
2494
+ return this.addTrafficMix(trafficMix);
2495
+ }
2259
2496
  /**
2260
2497
  * Applies a grouped configuration change to the current builder or context.
2261
2498
  * Use this when several related settings should be supplied in one step.
@@ -4408,6 +4645,111 @@ function attachLoadSimulationProjection(simulation) {
4408
4645
  });
4409
4646
  return simulation;
4410
4647
  }
4648
+ function expandTrafficMixScenarios(trafficMix) {
4649
+ if (!(trafficMix instanceof LoadStrikeTrafficMix)) {
4650
+ throw new TypeError("Traffic mix must be provided.");
4651
+ }
4652
+ const totalLoad = trafficMix.__loadStrikeTotalLoad();
4653
+ if (!totalLoad.length) {
4654
+ throw new Error("Traffic mix total load must be configured before registration.");
4655
+ }
4656
+ const scenarioMix = trafficMix.__loadStrikeScenarioMix();
4657
+ if (!scenarioMix.length) {
4658
+ throw new Error("Traffic mix scenario shares must be configured before registration.");
4659
+ }
4660
+ const weights = scenarioMix.map((share) => share.weight);
4661
+ return scenarioMix.map((share, index) => {
4662
+ const splitSimulations = totalLoad
4663
+ .map((simulation) => splitTrafficSimulation(simulation, weights, index))
4664
+ .filter((simulation) => simulation != null);
4665
+ const scenario = !splitSimulations.length
4666
+ ? share.scenario.withLoadSimulations(LoadStrikeSimulation.pause(0))
4667
+ : share.scenario.withLoadSimulations(...splitSimulations);
4668
+ return scenario.__loadStrikeWithInternalLicenseFeatures(TRAFFIC_MIX_FEATURE);
4669
+ });
4670
+ }
4671
+ function splitTrafficSimulation(simulation, weights, index) {
4672
+ const kind = String(simulation.Kind ?? "");
4673
+ const duringSeconds = readFiniteSimulationNumber(simulation, "DuringSeconds");
4674
+ const intervalSeconds = readFiniteSimulationNumber(simulation, "IntervalSeconds");
4675
+ if (kind === "Inject") {
4676
+ const rate = splitTrafficValue(readFiniteSimulationNumber(simulation, "Rate"), weights)[index];
4677
+ return rate > 0 ? LoadStrikeSimulation.inject(rate, intervalSeconds, duringSeconds) : null;
4678
+ }
4679
+ if (kind === "RampingInject") {
4680
+ const rate = splitTrafficValue(readFiniteSimulationNumber(simulation, "Rate"), weights)[index];
4681
+ return rate > 0 ? LoadStrikeSimulation.rampingInject(rate, intervalSeconds, duringSeconds) : null;
4682
+ }
4683
+ if (kind === "InjectRandom") {
4684
+ const minRate = splitTrafficValue(readFiniteSimulationNumber(simulation, "MinRate"), weights)[index];
4685
+ const maxRate = splitTrafficValue(readFiniteSimulationNumber(simulation, "MaxRate"), weights)[index];
4686
+ if (maxRate <= 0) {
4687
+ return null;
4688
+ }
4689
+ return LoadStrikeSimulation.injectRandom(Math.min(minRate, maxRate), maxRate, intervalSeconds, duringSeconds);
4690
+ }
4691
+ if (kind === "IterationsForInject") {
4692
+ const rate = splitTrafficValue(readFiniteSimulationNumber(simulation, "Rate"), weights)[index];
4693
+ const iterations = splitTrafficValue(readFiniteSimulationNumber(simulation, "Iterations"), weights)[index];
4694
+ return rate > 0 && iterations > 0
4695
+ ? LoadStrikeSimulation.iterationsForInject(rate, intervalSeconds, iterations)
4696
+ : null;
4697
+ }
4698
+ if (kind === "IterationsForConstant") {
4699
+ const copies = splitTrafficValue(readFiniteSimulationNumber(simulation, "Copies"), weights)[index];
4700
+ const iterations = splitTrafficValue(readFiniteSimulationNumber(simulation, "Iterations"), weights)[index];
4701
+ return copies > 0 && iterations > 0
4702
+ ? LoadStrikeSimulation.iterationsForConstant(copies, iterations)
4703
+ : null;
4704
+ }
4705
+ if (kind === "KeepConstant") {
4706
+ const copies = splitTrafficValue(readFiniteSimulationNumber(simulation, "Copies"), weights)[index];
4707
+ return copies > 0 ? LoadStrikeSimulation.keepConstant(copies, duringSeconds) : null;
4708
+ }
4709
+ if (kind === "RampingConstant") {
4710
+ const copies = splitTrafficValue(readFiniteSimulationNumber(simulation, "Copies"), weights)[index];
4711
+ return copies > 0 ? LoadStrikeSimulation.rampingConstant(copies, duringSeconds) : null;
4712
+ }
4713
+ if (kind === "Pause") {
4714
+ return LoadStrikeSimulation.pause(duringSeconds);
4715
+ }
4716
+ return attachLoadSimulationProjection({ ...simulation });
4717
+ }
4718
+ function splitTrafficValue(totalValue, weights) {
4719
+ const normalizedTotal = Math.trunc(Number.isFinite(totalValue) ? totalValue : 0);
4720
+ if (normalizedTotal <= 0) {
4721
+ return weights.map(() => 0);
4722
+ }
4723
+ const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
4724
+ if (totalWeight <= 0) {
4725
+ throw new Error("Traffic mix scenario weights must add up to more than zero.");
4726
+ }
4727
+ const exactShares = weights.map((weight, index) => {
4728
+ const exact = (normalizedTotal * weight) / totalWeight;
4729
+ return {
4730
+ index,
4731
+ value: Math.floor(exact),
4732
+ remainder: exact - Math.floor(exact)
4733
+ };
4734
+ });
4735
+ const split = exactShares.map((share) => share.value);
4736
+ let remaining = normalizedTotal - split.reduce((sum, value) => sum + value, 0);
4737
+ for (const share of [...exactShares].sort((left, right) => {
4738
+ const remainderDelta = right.remainder - left.remainder;
4739
+ return remainderDelta !== 0 ? remainderDelta : left.index - right.index;
4740
+ })) {
4741
+ if (remaining <= 0) {
4742
+ break;
4743
+ }
4744
+ split[share.index] += 1;
4745
+ remaining -= 1;
4746
+ }
4747
+ return split;
4748
+ }
4749
+ function readFiniteSimulationNumber(simulation, key) {
4750
+ const value = Number(simulation[key]);
4751
+ return Number.isFinite(value) ? value : 0;
4752
+ }
4411
4753
  function attachScenarioStatsAliases(scenario) {
4412
4754
  const normalized = normalizeScenarioStatsValue(scenario);
4413
4755
  normalized.ok = attachMeasurementStatsAliases(normalized.ok);
@@ -8366,6 +8708,7 @@ exports.__loadstrikeTestExports = {
8366
8708
  detailedToNodeStats,
8367
8709
  evaluateThresholdsForScenarios,
8368
8710
  executeTrackedScenarioInvocation,
8711
+ expandTrafficMixScenarios,
8369
8712
  extractContextOverridesFromArgs,
8370
8713
  extractContextOverridesFromConfig,
8371
8714
  findCaseInsensitiveKey,