@loadstrike/loadstrike-sdk 1.0.22801 → 1.0.23201

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
@@ -55,7 +55,7 @@ const result = await LoadStrikeRunner
55
55
 
56
56
  ## Trace-To-Test Autopilot
57
57
 
58
- Use `LoadStrikeAutopilot.generate(...)` to infer a starter plan from a captured artifact. 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`.
58
+ 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`.
59
59
 
60
60
  Use `SecretBindings` to map redaction locations such as `header:Authorization` or `body:$.client_secret` to environment variables, `TrackingSelector` when the selector cannot be inferred, and `EndpointBindings`, `AllowedReplayHosts`, or `BaseUrlRewrite` when a replay target must be bound. Secret values are resolved when the generated scenario runs; they are not written into the generated plan. Any gate satisfied by user setup is omitted from `ReadinessFailures`.
61
61
 
@@ -15,14 +15,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
15
15
  };
16
16
  var _LoadStrikeAutopilotResult_httpRequest;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.LoadStrikeAutopilot = exports.LoadStrikeAutopilotResult = void 0;
18
+ exports.__private = exports.LoadStrikeAutopilot = exports.LoadStrikeAutopilotResult = void 0;
19
19
  const node_fs_1 = __importDefault(require("node:fs"));
20
20
  const runtime_js_1 = require("./runtime.js");
21
+ const local_js_1 = require("./local.js");
21
22
  const autopilot_contracts_js_1 = require("./autopilot-contracts.js");
22
23
  const REDACTED = "[REDACTED]";
23
24
  const ENV_MARKER_PREFIX = "${LOADSTRIKE_ENV:";
24
25
  const ENV_MARKER_SUFFIX = "}";
25
26
  const TRACE_TO_TEST_AUTOPILOT_FEATURE = "autopilot.trace_to_test";
27
+ let autopilotLicenseValidationBypassForTests = false;
26
28
  const SECRET_KEYS = new Set([
27
29
  "authorization",
28
30
  "proxy_authorization",
@@ -95,9 +97,10 @@ class LoadStrikeAutopilotResult {
95
97
  exports.LoadStrikeAutopilotResult = LoadStrikeAutopilotResult;
96
98
  _LoadStrikeAutopilotResult_httpRequest = new WeakMap();
97
99
  class LoadStrikeAutopilot {
98
- static generate(request) {
100
+ static async generate(request) {
99
101
  const artifact = loadAutopilotArtifact(request);
100
102
  const options = request.Options ?? {};
103
+ await validateGenerationEntitlement(options);
101
104
  const result = inferAutopilotResult(artifact, options);
102
105
  if (options.IncludePreviewReport) {
103
106
  result.PreviewReport = buildPreviewReport(result);
@@ -109,6 +112,57 @@ class LoadStrikeAutopilot {
109
112
  }
110
113
  }
111
114
  exports.LoadStrikeAutopilot = LoadStrikeAutopilot;
115
+ exports.__private = {
116
+ setAutopilotLicenseValidationBypassForTests(value) {
117
+ autopilotLicenseValidationBypassForTests = value;
118
+ }
119
+ };
120
+ async function validateGenerationEntitlement(options) {
121
+ if (autopilotLicenseValidationBypassForTests) {
122
+ return;
123
+ }
124
+ const licensePayload = {
125
+ Context: {
126
+ RunnerKey: options.RunnerKey ?? "",
127
+ LicenseValidationTimeoutSeconds: options.LicenseValidationTimeoutSeconds,
128
+ TestSuite: "loadstrike-autopilot",
129
+ TestName: options.ScenarioName?.trim() || "autopilot-generation"
130
+ },
131
+ Scenarios: [
132
+ {
133
+ Name: "autopilot-generation",
134
+ InternalLicenseFeatures: [TRACE_TO_TEST_AUTOPILOT_FEATURE]
135
+ }
136
+ ],
137
+ RunArgs: []
138
+ };
139
+ const client = new local_js_1.LoadStrikeLocalClient({
140
+ licenseValidationTimeoutMs: normalizeLicenseValidationTimeoutMs(options.LicenseValidationTimeoutSeconds)
141
+ });
142
+ let session;
143
+ try {
144
+ session = await client.acquireLicenseLease(licensePayload);
145
+ }
146
+ catch (error) {
147
+ const message = error instanceof Error ? error.message : String(error);
148
+ if (message.toLowerCase().includes("runner key is required")) {
149
+ throw new Error("Runner key is required for Trace-To-Test Autopilot generation. " +
150
+ "Set Options.RunnerKey before calling LoadStrikeAutopilot.generate(...).");
151
+ }
152
+ throw error;
153
+ }
154
+ finally {
155
+ if (session) {
156
+ await client.releaseLicenseLease(session, licensePayload);
157
+ }
158
+ }
159
+ }
160
+ function normalizeLicenseValidationTimeoutMs(value) {
161
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
162
+ return undefined;
163
+ }
164
+ return Math.trunc(value * 1000);
165
+ }
112
166
  function loadAutopilotArtifact(request) {
113
167
  if (!request || typeof request.Kind !== "string" || !request.Kind.trim()) {
114
168
  throw new Error("Autopilot kind must be provided.");
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.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.EndpointAdapterFactory = 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.OtelCollectorReportingSinkOptions = exports.OtelCollectorReportingSink = exports.InfluxDbReportingSinkOptions = void 0;
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;
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; } });
@@ -37,7 +37,6 @@ Object.defineProperty(exports, "RedisCorrelationStore", { enumerable: true, get:
37
37
  Object.defineProperty(exports, "TrackingPayloadBuilder", { enumerable: true, get: function () { return correlation_js_1.TrackingPayloadBuilder; } });
38
38
  Object.defineProperty(exports, "TrackingFieldSelector", { enumerable: true, get: function () { return correlation_js_1.TrackingFieldSelector; } });
39
39
  var transports_js_1 = require("./transports.js");
40
- Object.defineProperty(exports, "EndpointAdapterFactory", { enumerable: true, get: function () { return transports_js_1.EndpointAdapterFactory; } });
41
40
  Object.defineProperty(exports, "LOADSTRIKE_TRACE_ID_HEADER", { enumerable: true, get: function () { return transports_js_1.LOADSTRIKE_TRACE_ID_HEADER; } });
42
41
  Object.defineProperty(exports, "LOADSTRIKE_TRACE_ID_TRACKING_FIELD", { enumerable: true, get: function () { return transports_js_1.LOADSTRIKE_TRACE_ID_TRACKING_FIELD; } });
43
42
  Object.defineProperty(exports, "TrafficEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.TrafficEndpointDefinition; } });
@@ -48,6 +47,7 @@ Object.defineProperty(exports, "RabbitMqEndpointDefinition", { enumerable: true,
48
47
  Object.defineProperty(exports, "NatsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.NatsEndpointDefinition; } });
49
48
  Object.defineProperty(exports, "RedisStreamsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.RedisStreamsEndpointDefinition; } });
50
49
  Object.defineProperty(exports, "AzureEventHubsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.AzureEventHubsEndpointDefinition; } });
50
+ Object.defineProperty(exports, "SqsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.SqsEndpointDefinition; } });
51
51
  Object.defineProperty(exports, "DelegateStreamEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.DelegateStreamEndpointDefinition; } });
52
52
  Object.defineProperty(exports, "PushDiffusionEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.PushDiffusionEndpointDefinition; } });
53
53
  Object.defineProperty(exports, "HttpOAuth2ClientCredentialsOptions", { enumerable: true, get: function () { return transports_js_1.HttpOAuth2ClientCredentialsOptions; } });
@@ -61,6 +61,7 @@ Object.defineProperty(exports, "InfluxDbReportingSink", { enumerable: true, get:
61
61
  Object.defineProperty(exports, "InfluxDbReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.InfluxDbReportingSinkOptions; } });
62
62
  Object.defineProperty(exports, "OtelCollectorReportingSink", { enumerable: true, get: function () { return sinks_js_1.OtelCollectorReportingSink; } });
63
63
  Object.defineProperty(exports, "OtelCollectorReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.OtelCollectorReportingSinkOptions; } });
64
+ Object.defineProperty(exports, "PortalReportingSink", { enumerable: true, get: function () { return sinks_js_1.PortalReportingSink; } });
64
65
  Object.defineProperty(exports, "SplunkReportingSink", { enumerable: true, get: function () { return sinks_js_1.SplunkReportingSink; } });
65
66
  Object.defineProperty(exports, "SplunkReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.SplunkReportingSinkOptions; } });
66
67
  Object.defineProperty(exports, "TimescaleDbReportingSink", { enumerable: true, get: function () { return sinks_js_1.TimescaleDbReportingSink; } });
package/dist/cjs/local.js CHANGED
@@ -44,7 +44,6 @@ const node_crypto_1 = require("node:crypto");
44
44
  const correlation_js_1 = require("./correlation.js");
45
45
  const transports_js_1 = require("./transports.js");
46
46
  const DEFAULT_LICENSING_API_BASE_URL = "https://licensing.loadstrike.com";
47
- const INTERNAL_BLACKBOX_LICENSING_API_BASE_URL_ENVIRONMENT_VARIABLE = "LOADSTRIKE_INTERNAL_BLACKBOX_API_BASE_URL";
48
47
  let developmentLicensingApiBaseUrlOverride;
49
48
  const BUILT_IN_WORKER_PLUGIN_NAMES = new Set([
50
49
  "loadstrike failed responses",
@@ -56,7 +55,8 @@ const SINK_FEATURE_BY_KIND = {
56
55
  grafanaloki: "extensions.reporting_sinks.grafana_loki",
57
56
  datadog: "extensions.reporting_sinks.datadog",
58
57
  splunk: "extensions.reporting_sinks.splunk",
59
- otelcollector: "extensions.reporting_sinks.otel_collector"
58
+ otelcollector: "extensions.reporting_sinks.otel_collector",
59
+ portal: "extensions.reporting_sinks.portal"
60
60
  };
61
61
  const TRACKING_FEATURE_BY_KIND = {
62
62
  http: "endpoint.http",
@@ -66,7 +66,8 @@ const TRACKING_FEATURE_BY_KIND = {
66
66
  pushdiffusion: "endpoint.push_diffusion",
67
67
  delegatestream: "endpoint.delegate_stream",
68
68
  nats: "endpoint.nats",
69
- redisstreams: "endpoint.redis_streams"
69
+ redisstreams: "endpoint.redis_streams",
70
+ sqs: "endpoint.sqs"
70
71
  };
71
72
  const CI_ENVIRONMENT_VARIABLES = [
72
73
  "GITHUB_ACTIONS",
@@ -96,6 +97,9 @@ class LoadStrikeLocalClient {
96
97
  this.licensingApiBaseUrl = resolveLicensingApiBaseUrl();
97
98
  this.licenseValidationTimeoutMs = normalizeTimeoutMs(options.licenseValidationTimeoutMs);
98
99
  }
100
+ portalReportingIngestUrl() {
101
+ return `${this.licensingApiBaseUrl.replace(/\/+$/, "")}/api/v1/reporting/ingest`;
102
+ }
99
103
  async run(request) {
100
104
  const sanitized = sanitizeRequest(request);
101
105
  const licenseSession = await this.acquireLicenseLease(sanitized);
@@ -506,25 +510,8 @@ function normalizeLicensingApiBaseUrl(value) {
506
510
  const normalized = (value ?? "").trim();
507
511
  return normalized || DEFAULT_LICENSING_API_BASE_URL;
508
512
  }
509
- function resolveInternalBlackboxLicensingApiBaseUrlOverride(value) {
510
- const normalized = String(value ?? "").trim();
511
- if (!normalized) {
512
- return undefined;
513
- }
514
- try {
515
- const parsed = new URL(normalized);
516
- const host = parsed.hostname.replace(/^\[|\]$/g, "").toLowerCase();
517
- if (["http:", "https:"].includes(parsed.protocol) && ["127.0.0.1", "localhost", "::1"].includes(host)) {
518
- return normalized.replace(/\/+$/, "");
519
- }
520
- }
521
- catch {
522
- return undefined;
523
- }
524
- return undefined;
525
- }
526
513
  function resolveLicensingApiBaseUrl() {
527
- return normalizeLicensingApiBaseUrl(resolveInternalBlackboxLicensingApiBaseUrlOverride(process.env[INTERNAL_BLACKBOX_LICENSING_API_BASE_URL_ENVIRONMENT_VARIABLE]) ?? developmentLicensingApiBaseUrlOverride);
514
+ return normalizeLicensingApiBaseUrl(developmentLicensingApiBaseUrlOverride);
528
515
  }
529
516
  function setDevelopmentLicensingApiBaseUrlOverride(value) {
530
517
  developmentLicensingApiBaseUrlOverride = value;
@@ -563,6 +550,9 @@ function collectRequestedFeatures(request) {
563
550
  if (countCustomWorkerPlugins(context) > 0) {
564
551
  features.add("extensions.worker_plugins.custom");
565
552
  }
553
+ if (asList(pickValue(context, "RuntimePolicies", "runtimePolicies")).length > 0) {
554
+ features.add("policy.runtime_controls");
555
+ }
566
556
  const reportingSinks = asList(pickValue(context, "ReportingSinks", "reportingSinks"));
567
557
  let hasCustomSink = false;
568
558
  for (const sink of reportingSinks) {
@@ -858,159 +848,171 @@ async function evaluateScenarioOutcome(scenario, requestCount, context) {
858
848
  validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndpoint);
859
849
  const sourceAdapter = transports_js_1.EndpointAdapterFactory.create(sourceEndpoint);
860
850
  const destinationAdapter = destinationEndpoint ? transports_js_1.EndpointAdapterFactory.create(destinationEndpoint) : null;
861
- const correlationTimeoutOverride = pickValue(tracking, "CorrelationTimeoutMs", "correlationTimeoutMs");
862
- const correlationTimeoutSeconds = pickValue(tracking, "CorrelationTimeoutSeconds", "correlationTimeoutSeconds", "CorrelationTimeout");
863
- const timeoutMs = correlationTimeoutOverride != null && String(correlationTimeoutOverride).trim() !== ""
864
- ? asInt(correlationTimeoutOverride)
865
- : Math.trunc((correlationTimeoutSeconds == null || String(correlationTimeoutSeconds).trim() === ""
866
- ? 30
867
- : asNumber(correlationTimeoutSeconds)) * 1000);
868
- const timeoutCountsAsFailure = toBoolean(pickValue(tracking, "TimeoutCountsAsFailure", "timeoutCountsAsFailure"), true);
869
- const correlationStore = mapCorrelationStore(tracking, buildTrackingRunNamespace(stringOrDefault(pickValue(context, "SessionId", "sessionId"), "session"), stringOrDefault(pickValue(scenario, "Name", "name"), "scenario"), sourceEndpoint.name, destinationEndpoint?.name));
870
- const timeoutSweepIntervalOverride = pickValue(tracking, "TimeoutSweepIntervalMs", "timeoutSweepIntervalMs");
871
- const timeoutSweepIntervalSeconds = pickValue(tracking, "TimeoutSweepIntervalSeconds", "timeoutSweepIntervalSeconds");
872
- const timeoutSweepIntervalMs = timeoutSweepIntervalOverride != null && String(timeoutSweepIntervalOverride).trim() !== ""
873
- ? asInt(timeoutSweepIntervalOverride)
874
- : Math.trunc((timeoutSweepIntervalSeconds == null || String(timeoutSweepIntervalSeconds).trim() === ""
875
- ? 1
876
- : asNumber(timeoutSweepIntervalSeconds)) * 1000);
877
- const timeoutBatchSizeValue = pickValue(tracking, "TimeoutBatchSize", "timeoutBatchSize");
878
- const timeoutBatchSize = timeoutBatchSizeValue == null || String(timeoutBatchSizeValue).trim() === ""
879
- ? 200
880
- : asInt(timeoutBatchSizeValue);
881
- const runtime = new correlation_js_1.CrossPlatformTrackingRuntime({
882
- sourceTrackingField: sourceEndpoint.trackingField,
883
- destinationTrackingField: destinationEndpoint?.trackingField,
884
- destinationGatherByField: destinationEndpoint?.gatherByField,
885
- trackingFieldValueCaseSensitive: toBoolean(pickValue(tracking, "TrackingFieldValueCaseSensitive", "trackingFieldValueCaseSensitive"), true),
886
- gatherByFieldValueCaseSensitive: toBoolean(pickValue(tracking, "GatherByFieldValueCaseSensitive", "gatherByFieldValueCaseSensitive"), true),
887
- correlationTimeoutMs: timeoutMs,
888
- timeoutCountsAsFailure,
889
- store: correlationStore ?? undefined
890
- });
891
- const runMode = stringOrDefault(pickValue(tracking, "RunMode", "runMode"), "GenerateAndCorrelate").trim().toLowerCase();
892
- const sourceOnlyMode = !destinationEndpoint;
893
- const restartOnFail = toBoolean(pickValue(scenario, "RestartIterationOnFail", "restartIterationOnFail"), false);
894
- const restartMaxAttempts = Math.max(asInt(pickValue(context, "RestartIterationMaxAttempts", "restartIterationMaxAttempts")), 0);
895
- const maxFailCount = Math.max(asInt(pickValue(scenario, "MaxFailCount", "maxFailCount")), 0);
896
- const scenarioCompletionTimeoutSeconds = Math.max(asNumber(pickValue(context, "ScenarioCompletionTimeoutSeconds", "scenarioCompletionTimeoutSeconds")), 0);
897
- const scenarioDeadlineMs = scenarioCompletionTimeoutSeconds > 0
898
- ? Date.now() + Math.trunc(scenarioCompletionTimeoutSeconds * 1000)
899
- : Number.POSITIVE_INFINITY;
900
- let okCount = 0;
901
- let failCount = 0;
902
- let nowMs = Date.now();
903
- let processedIterations = 0;
904
- let lastSweepAtMs = nowMs;
905
- for (let i = 0; i < requestCount; i += 1) {
906
- if (Date.now() > scenarioDeadlineMs) {
907
- break;
908
- }
909
- let iterationComplete = false;
910
- let attempts = 0;
911
- const maxAttempts = 1 + (restartOnFail ? restartMaxAttempts : 0);
912
- while (!iterationComplete) {
913
- attempts += 1;
851
+ let correlationStore = null;
852
+ try {
853
+ await sourceAdapter.initialize?.();
854
+ await destinationAdapter?.initialize?.();
855
+ const correlationTimeoutOverride = pickValue(tracking, "CorrelationTimeoutMs", "correlationTimeoutMs");
856
+ const correlationTimeoutSeconds = pickValue(tracking, "CorrelationTimeoutSeconds", "correlationTimeoutSeconds", "CorrelationTimeout");
857
+ const timeoutMs = correlationTimeoutOverride != null && String(correlationTimeoutOverride).trim() !== ""
858
+ ? asInt(correlationTimeoutOverride)
859
+ : Math.trunc((correlationTimeoutSeconds == null || String(correlationTimeoutSeconds).trim() === ""
860
+ ? 30
861
+ : asNumber(correlationTimeoutSeconds)) * 1000);
862
+ const timeoutCountsAsFailure = toBoolean(pickValue(tracking, "TimeoutCountsAsFailure", "timeoutCountsAsFailure"), true);
863
+ correlationStore = mapCorrelationStore(tracking, buildTrackingRunNamespace(stringOrDefault(pickValue(context, "SessionId", "sessionId"), "session"), stringOrDefault(pickValue(scenario, "Name", "name"), "scenario"), sourceEndpoint.name, destinationEndpoint?.name));
864
+ const timeoutSweepIntervalOverride = pickValue(tracking, "TimeoutSweepIntervalMs", "timeoutSweepIntervalMs");
865
+ const timeoutSweepIntervalSeconds = pickValue(tracking, "TimeoutSweepIntervalSeconds", "timeoutSweepIntervalSeconds");
866
+ const timeoutSweepIntervalMs = timeoutSweepIntervalOverride != null && String(timeoutSweepIntervalOverride).trim() !== ""
867
+ ? asInt(timeoutSweepIntervalOverride)
868
+ : Math.trunc((timeoutSweepIntervalSeconds == null || String(timeoutSweepIntervalSeconds).trim() === ""
869
+ ? 1
870
+ : asNumber(timeoutSweepIntervalSeconds)) * 1000);
871
+ const timeoutBatchSizeValue = pickValue(tracking, "TimeoutBatchSize", "timeoutBatchSize");
872
+ const timeoutBatchSize = timeoutBatchSizeValue == null || String(timeoutBatchSizeValue).trim() === ""
873
+ ? 200
874
+ : asInt(timeoutBatchSizeValue);
875
+ const runtime = new correlation_js_1.CrossPlatformTrackingRuntime({
876
+ sourceTrackingField: sourceEndpoint.trackingField,
877
+ destinationTrackingField: destinationEndpoint?.trackingField,
878
+ destinationGatherByField: destinationEndpoint?.gatherByField,
879
+ trackingFieldValueCaseSensitive: toBoolean(pickValue(tracking, "TrackingFieldValueCaseSensitive", "trackingFieldValueCaseSensitive"), true),
880
+ gatherByFieldValueCaseSensitive: toBoolean(pickValue(tracking, "GatherByFieldValueCaseSensitive", "gatherByFieldValueCaseSensitive"), true),
881
+ correlationTimeoutMs: timeoutMs,
882
+ timeoutCountsAsFailure,
883
+ store: correlationStore ?? undefined
884
+ });
885
+ const runMode = stringOrDefault(pickValue(tracking, "RunMode", "runMode"), "GenerateAndCorrelate").trim().toLowerCase();
886
+ const sourceOnlyMode = !destinationEndpoint;
887
+ const restartOnFail = toBoolean(pickValue(scenario, "RestartIterationOnFail", "restartIterationOnFail"), false);
888
+ const restartMaxAttempts = Math.max(asInt(pickValue(context, "RestartIterationMaxAttempts", "restartIterationMaxAttempts")), 0);
889
+ const maxFailCount = Math.max(asInt(pickValue(scenario, "MaxFailCount", "maxFailCount")), 0);
890
+ const scenarioCompletionTimeoutSeconds = Math.max(asNumber(pickValue(context, "ScenarioCompletionTimeoutSeconds", "scenarioCompletionTimeoutSeconds")), 0);
891
+ const scenarioDeadlineMs = scenarioCompletionTimeoutSeconds > 0
892
+ ? Date.now() + Math.trunc(scenarioCompletionTimeoutSeconds * 1000)
893
+ : Number.POSITIVE_INFINITY;
894
+ let okCount = 0;
895
+ let failCount = 0;
896
+ let nowMs = Date.now();
897
+ let processedIterations = 0;
898
+ let lastSweepAtMs = nowMs;
899
+ for (let i = 0; i < requestCount; i += 1) {
914
900
  if (Date.now() > scenarioDeadlineMs) {
915
- iterationComplete = true;
916
901
  break;
917
902
  }
918
- let sourcePayload = runMode === "correlateexistingtraffic"
919
- ? await sourceAdapter.consume()
920
- : await sourceAdapter.produce();
921
- sourcePayload = normalizePayload(sourcePayload, sourceEndpoint, i);
922
- const sourceTrackingId = sourceOnlyMode
923
- ? readTrackingId(sourcePayload, sourceEndpoint.trackingField)
924
- : await runtime.onSourceProduced(sourcePayload, nowMs);
925
- if (!sourceTrackingId) {
926
- if (restartOnFail && attempts < maxAttempts) {
903
+ let iterationComplete = false;
904
+ let attempts = 0;
905
+ const maxAttempts = 1 + (restartOnFail ? restartMaxAttempts : 0);
906
+ while (!iterationComplete) {
907
+ attempts += 1;
908
+ if (Date.now() > scenarioDeadlineMs) {
909
+ iterationComplete = true;
910
+ break;
911
+ }
912
+ let sourcePayload = runMode === "correlateexistingtraffic"
913
+ ? await sourceAdapter.consume()
914
+ : await sourceAdapter.produce();
915
+ sourcePayload = normalizePayload(sourcePayload, sourceEndpoint, i);
916
+ const sourceTrackingId = sourceOnlyMode
917
+ ? readTrackingId(sourcePayload, sourceEndpoint.trackingField)
918
+ : await runtime.onSourceProduced(sourcePayload, nowMs);
919
+ if (!sourceTrackingId) {
920
+ if (restartOnFail && attempts < maxAttempts) {
921
+ nowMs += 1;
922
+ continue;
923
+ }
924
+ failCount += 1;
925
+ iterationComplete = true;
927
926
  nowMs += 1;
928
- continue;
927
+ break;
929
928
  }
930
- failCount += 1;
931
- iterationComplete = true;
932
- nowMs += 1;
933
- break;
934
- }
935
- if (sourceOnlyMode || !destinationAdapter || !destinationEndpoint) {
936
- okCount += 1;
937
- iterationComplete = true;
938
- nowMs += 1;
939
- break;
940
- }
941
- let destinationPayload = await destinationAdapter.consume();
942
- destinationPayload = normalizePayload(destinationPayload, destinationEndpoint, i);
943
- let matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
944
- const correlationDeadlineMs = nowMs + timeoutMs;
945
- while (!matched && Date.now() <= scenarioDeadlineMs && nowMs < correlationDeadlineMs) {
946
- const remainingTimeoutMs = correlationDeadlineMs - nowMs;
947
- if (remainingTimeoutMs <= 0) {
929
+ if (sourceOnlyMode || !destinationAdapter || !destinationEndpoint) {
930
+ okCount += 1;
931
+ iterationComplete = true;
932
+ nowMs += 1;
948
933
  break;
949
934
  }
950
- await sleep(Math.min(destinationEndpoint.pollIntervalMs ?? 250, remainingTimeoutMs));
951
- destinationPayload = normalizePayload(await destinationAdapter.consume(), destinationEndpoint, i);
952
- matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
935
+ let destinationPayload = await destinationAdapter.consume();
936
+ destinationPayload = normalizePayload(destinationPayload, destinationEndpoint, i);
937
+ let matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
938
+ const correlationDeadlineMs = nowMs + timeoutMs;
939
+ while (!matched && Date.now() <= scenarioDeadlineMs && nowMs < correlationDeadlineMs) {
940
+ const remainingTimeoutMs = correlationDeadlineMs - nowMs;
941
+ if (remainingTimeoutMs <= 0) {
942
+ break;
943
+ }
944
+ await sleep(Math.min(destinationEndpoint.pollIntervalMs ?? 250, remainingTimeoutMs));
945
+ destinationPayload = normalizePayload(await destinationAdapter.consume(), destinationEndpoint, i);
946
+ matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
947
+ if (matched) {
948
+ break;
949
+ }
950
+ nowMs += Math.max(destinationEndpoint.pollIntervalMs ?? 250, 1);
951
+ }
953
952
  if (matched) {
954
- break;
953
+ okCount += 1;
954
+ iterationComplete = true;
955
955
  }
956
- nowMs += Math.max(destinationEndpoint.pollIntervalMs ?? 250, 1);
957
- }
958
- if (matched) {
959
- okCount += 1;
960
- iterationComplete = true;
961
- }
962
- else if (restartOnFail && attempts < maxAttempts) {
963
- nowMs += 2;
964
- continue;
965
- }
966
- else {
967
- await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
968
- failCount += 1;
969
- iterationComplete = true;
970
- }
971
- nowMs += 2;
972
- }
973
- processedIterations += 1;
974
- if (timeoutSweepIntervalMs > 0 && nowMs - lastSweepAtMs >= timeoutSweepIntervalMs) {
975
- const swept = await runtime.sweepTimeouts(nowMs, timeoutBatchSize || undefined);
976
- if (swept > 0) {
977
- if (timeoutCountsAsFailure) {
978
- failCount += swept;
956
+ else if (restartOnFail && attempts < maxAttempts) {
957
+ nowMs += 2;
958
+ continue;
979
959
  }
980
960
  else {
981
- okCount += swept;
961
+ await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
962
+ failCount += 1;
963
+ iterationComplete = true;
964
+ }
965
+ nowMs += 2;
966
+ }
967
+ processedIterations += 1;
968
+ if (timeoutSweepIntervalMs > 0 && nowMs - lastSweepAtMs >= timeoutSweepIntervalMs) {
969
+ const swept = await runtime.sweepTimeouts(nowMs, timeoutBatchSize || undefined);
970
+ if (swept > 0) {
971
+ if (timeoutCountsAsFailure) {
972
+ failCount += swept;
973
+ }
974
+ else {
975
+ okCount += swept;
976
+ }
982
977
  }
978
+ lastSweepAtMs = nowMs;
979
+ }
980
+ if (maxFailCount > 0 && failCount >= maxFailCount) {
981
+ break;
983
982
  }
984
- lastSweepAtMs = nowMs;
985
- }
986
- if (maxFailCount > 0 && failCount >= maxFailCount) {
987
- break;
988
983
  }
989
- }
990
- let expired = 0;
991
- let sweptChunk = 0;
992
- do {
993
- sweptChunk = await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
994
- expired += sweptChunk;
995
- } while (timeoutBatchSize > 0 && sweptChunk >= timeoutBatchSize);
996
- if (expired > 0) {
997
- if (timeoutCountsAsFailure) {
998
- failCount += expired;
984
+ let expired = 0;
985
+ let sweptChunk = 0;
986
+ do {
987
+ sweptChunk = await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
988
+ expired += sweptChunk;
989
+ } while (timeoutBatchSize > 0 && sweptChunk >= timeoutBatchSize);
990
+ if (expired > 0) {
991
+ if (timeoutCountsAsFailure) {
992
+ failCount += expired;
993
+ }
994
+ else {
995
+ okCount += expired;
996
+ }
999
997
  }
1000
- else {
1001
- okCount += expired;
998
+ if (processedIterations < requestCount && Date.now() > scenarioDeadlineMs) {
999
+ const timedOutIterations = requestCount - processedIterations;
1000
+ if (timeoutCountsAsFailure) {
1001
+ failCount += timedOutIterations;
1002
+ }
1003
+ else {
1004
+ okCount += timedOutIterations;
1005
+ }
1002
1006
  }
1007
+ return { requestCount: processedIterations, okCount, failCount };
1003
1008
  }
1004
- if (processedIterations < requestCount && Date.now() > scenarioDeadlineMs) {
1005
- const timedOutIterations = requestCount - processedIterations;
1006
- if (timeoutCountsAsFailure) {
1007
- failCount += timedOutIterations;
1008
- }
1009
- else {
1010
- okCount += timedOutIterations;
1011
- }
1009
+ finally {
1010
+ await Promise.allSettled([
1011
+ sourceAdapter.dispose?.(),
1012
+ destinationAdapter?.dispose?.(),
1013
+ correlationStore?.close?.()
1014
+ ].filter((value) => Boolean(value)));
1012
1015
  }
1013
- return { requestCount: processedIterations, okCount, failCount };
1014
1016
  }
1015
1017
  function validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndpoint) {
1016
1018
  if (sourceEndpoint.gatherByField?.trim()) {
@@ -13,6 +13,7 @@ const cluster_js_1 = require("./cluster.js");
13
13
  const correlation_js_1 = require("./correlation.js");
14
14
  const transports_js_1 = require("./transports.js");
15
15
  const reporting_js_1 = require("./reporting.js");
16
+ const sinks_js_1 = require("./sinks.js");
16
17
  exports.LoadStrikeNodeType = {
17
18
  SingleNode: "SingleNode",
18
19
  Coordinator: "Coordinator",
@@ -1395,6 +1396,16 @@ class LoadStrikeContext {
1395
1396
  validateNamedReportingSinks(sinks);
1396
1397
  return this.mergeValues({ ReportingSinks: sinks });
1397
1398
  }
1399
+ withPortalReporting() {
1400
+ return this.WithPortalReporting();
1401
+ }
1402
+ WithPortalReporting() {
1403
+ const sinks = [...(this.values.ReportingSinks ?? [])];
1404
+ if (!sinks.some((sink, index) => resolveSinkName(sink, index).trim().toLowerCase() === "portal")) {
1405
+ sinks.push(new sinks_js_1.PortalReportingSink());
1406
+ }
1407
+ return this.mergeValues({ ReportingSinks: sinks });
1408
+ }
1398
1409
  /**
1399
1410
  * Registers runtime policies for the run.
1400
1411
  * Use this when scenario selection or step execution should obey policy callbacks.
@@ -2137,6 +2148,9 @@ class LoadStrikeRunner {
2137
2148
  static WithReportingSinks(context, ...sinks) {
2138
2149
  return context.WithReportingSinks(...sinks);
2139
2150
  }
2151
+ static WithPortalReporting(context) {
2152
+ return context.WithPortalReporting();
2153
+ }
2140
2154
  /**
2141
2155
  * Registers runtime policies for the run.
2142
2156
  * Use this when scenario selection or step execution should obey policy callbacks.
@@ -2365,6 +2379,29 @@ class LoadStrikeRunner {
2365
2379
  WithReportingInterval(intervalSeconds) {
2366
2380
  return this.withReportingInterval(intervalSeconds);
2367
2381
  }
2382
+ withReportingSinks(...sinks) {
2383
+ if (!sinks.length) {
2384
+ throw new Error("At least one reporting sink should be provided.");
2385
+ }
2386
+ if (sinks.some((sink) => sink == null)) {
2387
+ throw new Error("Reporting sink collection cannot contain null values.");
2388
+ }
2389
+ validateNamedReportingSinks(sinks);
2390
+ return this.configure({ reportingSinks: sinks });
2391
+ }
2392
+ WithReportingSinks(...sinks) {
2393
+ return this.withReportingSinks(...sinks);
2394
+ }
2395
+ withPortalReporting() {
2396
+ const sinks = [...(this.options.reportingSinks ?? [])];
2397
+ if (!sinks.some((sink, index) => resolveSinkName(sink, index).trim().toLowerCase() === "portal")) {
2398
+ sinks.push(new sinks_js_1.PortalReportingSink());
2399
+ }
2400
+ return this.configure({ reportingSinks: sinks });
2401
+ }
2402
+ WithPortalReporting() {
2403
+ return this.withPortalReporting();
2404
+ }
2368
2405
  /**
2369
2406
  * Sets the timeout for cluster command round-trips.
2370
2407
  * Use this when distributed control messages need a tighter or looser deadline.
@@ -2538,6 +2575,7 @@ class LoadStrikeRunner {
2538
2575
  testInfo,
2539
2576
  getNodeInfo: () => attachNodeInfoAliases({ ...nodeInfo })
2540
2577
  };
2578
+ attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, licenseSession);
2541
2579
  attachSessionStartInfoAliases(sessionInfo);
2542
2580
  nodeInfo.currentOperation = "Init";
2543
2581
  for (const plugin of plugins) {
@@ -4072,10 +4110,23 @@ function attachSessionStartInfoAliases(session) {
4072
4110
  attachAliasMap(session, {
4073
4111
  StartedUtc: "startedUtc",
4074
4112
  ScenarioNames: "scenarioNames",
4075
- Scenarios: "scenarios"
4113
+ Scenarios: "scenarios",
4114
+ RunToken: "runToken",
4115
+ PortalReportingIngestUrl: "portalReportingIngestUrl"
4076
4116
  });
4077
4117
  return session;
4078
4118
  }
4119
+ function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, licenseSession) {
4120
+ const runToken = stringValueOrDefault(licenseSession?.runToken, "").trim();
4121
+ if (!runToken || !licenseClient) {
4122
+ return;
4123
+ }
4124
+ const ingestUrl = licenseClient.portalReportingIngestUrl();
4125
+ sinkSession.runToken = runToken;
4126
+ sinkSession.portalReportingIngestUrl = ingestUrl;
4127
+ sessionInfo.runToken = runToken;
4128
+ sessionInfo.portalReportingIngestUrl = ingestUrl;
4129
+ }
4079
4130
  function attachScenarioInitContextAliases(context) {
4080
4131
  context.nodeInfo = attachNodeInfoAliases(context.nodeInfo);
4081
4132
  context.testInfo = attachTestInfoAliases(context.testInfo);
@@ -6626,6 +6677,7 @@ function mapRuntimeTrackingEndpointSpec(spec, useLoadStrikeTraceIdHeader = false
6626
6677
  nats: asTrackingRecord(pickTrackingValue(spec, "Nats", "nats")),
6627
6678
  redisStreams: asTrackingRecord(pickTrackingValue(spec, "RedisStreams", "redisStreams")),
6628
6679
  azureEventHubs: asTrackingRecord(pickTrackingValue(spec, "AzureEventHubs", "azureEventHubs")),
6680
+ sqs: asTrackingRecord(pickTrackingValue(spec, "Sqs", "sqs")),
6629
6681
  pushDiffusion: asTrackingRecord(pickTrackingValue(spec, "PushDiffusion", "pushDiffusion")),
6630
6682
  delegate: typeof delegateProduce === "function"
6631
6683
  || typeof delegateConsume === "function"