@loadstrike/loadstrike-sdk 1.0.22801 → 1.0.23001

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,6 +1,6 @@
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;
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
4
  exports.TimescaleDbReportingSinkOptions = exports.TimescaleDbReportingSink = exports.SplunkReportingSinkOptions = exports.SplunkReportingSink = 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; } });
@@ -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; } });
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",
@@ -66,7 +65,8 @@ const TRACKING_FEATURE_BY_KIND = {
66
65
  pushdiffusion: "endpoint.push_diffusion",
67
66
  delegatestream: "endpoint.delegate_stream",
68
67
  nats: "endpoint.nats",
69
- redisstreams: "endpoint.redis_streams"
68
+ redisstreams: "endpoint.redis_streams",
69
+ sqs: "endpoint.sqs"
70
70
  };
71
71
  const CI_ENVIRONMENT_VARIABLES = [
72
72
  "GITHUB_ACTIONS",
@@ -506,25 +506,8 @@ function normalizeLicensingApiBaseUrl(value) {
506
506
  const normalized = (value ?? "").trim();
507
507
  return normalized || DEFAULT_LICENSING_API_BASE_URL;
508
508
  }
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
509
  function resolveLicensingApiBaseUrl() {
527
- return normalizeLicensingApiBaseUrl(resolveInternalBlackboxLicensingApiBaseUrlOverride(process.env[INTERNAL_BLACKBOX_LICENSING_API_BASE_URL_ENVIRONMENT_VARIABLE]) ?? developmentLicensingApiBaseUrlOverride);
510
+ return normalizeLicensingApiBaseUrl(developmentLicensingApiBaseUrlOverride);
528
511
  }
529
512
  function setDevelopmentLicensingApiBaseUrlOverride(value) {
530
513
  developmentLicensingApiBaseUrlOverride = value;
@@ -563,6 +546,9 @@ function collectRequestedFeatures(request) {
563
546
  if (countCustomWorkerPlugins(context) > 0) {
564
547
  features.add("extensions.worker_plugins.custom");
565
548
  }
549
+ if (asList(pickValue(context, "RuntimePolicies", "runtimePolicies")).length > 0) {
550
+ features.add("policy.runtime_controls");
551
+ }
566
552
  const reportingSinks = asList(pickValue(context, "ReportingSinks", "reportingSinks"));
567
553
  let hasCustomSink = false;
568
554
  for (const sink of reportingSinks) {
@@ -858,159 +844,171 @@ async function evaluateScenarioOutcome(scenario, requestCount, context) {
858
844
  validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndpoint);
859
845
  const sourceAdapter = transports_js_1.EndpointAdapterFactory.create(sourceEndpoint);
860
846
  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;
847
+ let correlationStore = null;
848
+ try {
849
+ await sourceAdapter.initialize?.();
850
+ await destinationAdapter?.initialize?.();
851
+ const correlationTimeoutOverride = pickValue(tracking, "CorrelationTimeoutMs", "correlationTimeoutMs");
852
+ const correlationTimeoutSeconds = pickValue(tracking, "CorrelationTimeoutSeconds", "correlationTimeoutSeconds", "CorrelationTimeout");
853
+ const timeoutMs = correlationTimeoutOverride != null && String(correlationTimeoutOverride).trim() !== ""
854
+ ? asInt(correlationTimeoutOverride)
855
+ : Math.trunc((correlationTimeoutSeconds == null || String(correlationTimeoutSeconds).trim() === ""
856
+ ? 30
857
+ : asNumber(correlationTimeoutSeconds)) * 1000);
858
+ const timeoutCountsAsFailure = toBoolean(pickValue(tracking, "TimeoutCountsAsFailure", "timeoutCountsAsFailure"), true);
859
+ correlationStore = mapCorrelationStore(tracking, buildTrackingRunNamespace(stringOrDefault(pickValue(context, "SessionId", "sessionId"), "session"), stringOrDefault(pickValue(scenario, "Name", "name"), "scenario"), sourceEndpoint.name, destinationEndpoint?.name));
860
+ const timeoutSweepIntervalOverride = pickValue(tracking, "TimeoutSweepIntervalMs", "timeoutSweepIntervalMs");
861
+ const timeoutSweepIntervalSeconds = pickValue(tracking, "TimeoutSweepIntervalSeconds", "timeoutSweepIntervalSeconds");
862
+ const timeoutSweepIntervalMs = timeoutSweepIntervalOverride != null && String(timeoutSweepIntervalOverride).trim() !== ""
863
+ ? asInt(timeoutSweepIntervalOverride)
864
+ : Math.trunc((timeoutSweepIntervalSeconds == null || String(timeoutSweepIntervalSeconds).trim() === ""
865
+ ? 1
866
+ : asNumber(timeoutSweepIntervalSeconds)) * 1000);
867
+ const timeoutBatchSizeValue = pickValue(tracking, "TimeoutBatchSize", "timeoutBatchSize");
868
+ const timeoutBatchSize = timeoutBatchSizeValue == null || String(timeoutBatchSizeValue).trim() === ""
869
+ ? 200
870
+ : asInt(timeoutBatchSizeValue);
871
+ const runtime = new correlation_js_1.CrossPlatformTrackingRuntime({
872
+ sourceTrackingField: sourceEndpoint.trackingField,
873
+ destinationTrackingField: destinationEndpoint?.trackingField,
874
+ destinationGatherByField: destinationEndpoint?.gatherByField,
875
+ trackingFieldValueCaseSensitive: toBoolean(pickValue(tracking, "TrackingFieldValueCaseSensitive", "trackingFieldValueCaseSensitive"), true),
876
+ gatherByFieldValueCaseSensitive: toBoolean(pickValue(tracking, "GatherByFieldValueCaseSensitive", "gatherByFieldValueCaseSensitive"), true),
877
+ correlationTimeoutMs: timeoutMs,
878
+ timeoutCountsAsFailure,
879
+ store: correlationStore ?? undefined
880
+ });
881
+ const runMode = stringOrDefault(pickValue(tracking, "RunMode", "runMode"), "GenerateAndCorrelate").trim().toLowerCase();
882
+ const sourceOnlyMode = !destinationEndpoint;
883
+ const restartOnFail = toBoolean(pickValue(scenario, "RestartIterationOnFail", "restartIterationOnFail"), false);
884
+ const restartMaxAttempts = Math.max(asInt(pickValue(context, "RestartIterationMaxAttempts", "restartIterationMaxAttempts")), 0);
885
+ const maxFailCount = Math.max(asInt(pickValue(scenario, "MaxFailCount", "maxFailCount")), 0);
886
+ const scenarioCompletionTimeoutSeconds = Math.max(asNumber(pickValue(context, "ScenarioCompletionTimeoutSeconds", "scenarioCompletionTimeoutSeconds")), 0);
887
+ const scenarioDeadlineMs = scenarioCompletionTimeoutSeconds > 0
888
+ ? Date.now() + Math.trunc(scenarioCompletionTimeoutSeconds * 1000)
889
+ : Number.POSITIVE_INFINITY;
890
+ let okCount = 0;
891
+ let failCount = 0;
892
+ let nowMs = Date.now();
893
+ let processedIterations = 0;
894
+ let lastSweepAtMs = nowMs;
895
+ for (let i = 0; i < requestCount; i += 1) {
914
896
  if (Date.now() > scenarioDeadlineMs) {
915
- iterationComplete = true;
916
897
  break;
917
898
  }
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) {
899
+ let iterationComplete = false;
900
+ let attempts = 0;
901
+ const maxAttempts = 1 + (restartOnFail ? restartMaxAttempts : 0);
902
+ while (!iterationComplete) {
903
+ attempts += 1;
904
+ if (Date.now() > scenarioDeadlineMs) {
905
+ iterationComplete = true;
906
+ break;
907
+ }
908
+ let sourcePayload = runMode === "correlateexistingtraffic"
909
+ ? await sourceAdapter.consume()
910
+ : await sourceAdapter.produce();
911
+ sourcePayload = normalizePayload(sourcePayload, sourceEndpoint, i);
912
+ const sourceTrackingId = sourceOnlyMode
913
+ ? readTrackingId(sourcePayload, sourceEndpoint.trackingField)
914
+ : await runtime.onSourceProduced(sourcePayload, nowMs);
915
+ if (!sourceTrackingId) {
916
+ if (restartOnFail && attempts < maxAttempts) {
917
+ nowMs += 1;
918
+ continue;
919
+ }
920
+ failCount += 1;
921
+ iterationComplete = true;
927
922
  nowMs += 1;
928
- continue;
923
+ break;
929
924
  }
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) {
925
+ if (sourceOnlyMode || !destinationAdapter || !destinationEndpoint) {
926
+ okCount += 1;
927
+ iterationComplete = true;
928
+ nowMs += 1;
948
929
  break;
949
930
  }
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);
931
+ let destinationPayload = await destinationAdapter.consume();
932
+ destinationPayload = normalizePayload(destinationPayload, destinationEndpoint, i);
933
+ let matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
934
+ const correlationDeadlineMs = nowMs + timeoutMs;
935
+ while (!matched && Date.now() <= scenarioDeadlineMs && nowMs < correlationDeadlineMs) {
936
+ const remainingTimeoutMs = correlationDeadlineMs - nowMs;
937
+ if (remainingTimeoutMs <= 0) {
938
+ break;
939
+ }
940
+ await sleep(Math.min(destinationEndpoint.pollIntervalMs ?? 250, remainingTimeoutMs));
941
+ destinationPayload = normalizePayload(await destinationAdapter.consume(), destinationEndpoint, i);
942
+ matched = await runtime.onDestinationConsumed(destinationPayload, nowMs + 1);
943
+ if (matched) {
944
+ break;
945
+ }
946
+ nowMs += Math.max(destinationEndpoint.pollIntervalMs ?? 250, 1);
947
+ }
953
948
  if (matched) {
954
- break;
949
+ okCount += 1;
950
+ iterationComplete = true;
955
951
  }
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;
952
+ else if (restartOnFail && attempts < maxAttempts) {
953
+ nowMs += 2;
954
+ continue;
979
955
  }
980
956
  else {
981
- okCount += swept;
957
+ await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
958
+ failCount += 1;
959
+ iterationComplete = true;
982
960
  }
961
+ nowMs += 2;
962
+ }
963
+ processedIterations += 1;
964
+ if (timeoutSweepIntervalMs > 0 && nowMs - lastSweepAtMs >= timeoutSweepIntervalMs) {
965
+ const swept = await runtime.sweepTimeouts(nowMs, timeoutBatchSize || undefined);
966
+ if (swept > 0) {
967
+ if (timeoutCountsAsFailure) {
968
+ failCount += swept;
969
+ }
970
+ else {
971
+ okCount += swept;
972
+ }
973
+ }
974
+ lastSweepAtMs = nowMs;
975
+ }
976
+ if (maxFailCount > 0 && failCount >= maxFailCount) {
977
+ break;
983
978
  }
984
- lastSweepAtMs = nowMs;
985
- }
986
- if (maxFailCount > 0 && failCount >= maxFailCount) {
987
- break;
988
979
  }
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;
980
+ let expired = 0;
981
+ let sweptChunk = 0;
982
+ do {
983
+ sweptChunk = await runtime.sweepTimeouts(nowMs + timeoutMs + 1, timeoutBatchSize || undefined);
984
+ expired += sweptChunk;
985
+ } while (timeoutBatchSize > 0 && sweptChunk >= timeoutBatchSize);
986
+ if (expired > 0) {
987
+ if (timeoutCountsAsFailure) {
988
+ failCount += expired;
989
+ }
990
+ else {
991
+ okCount += expired;
992
+ }
999
993
  }
1000
- else {
1001
- okCount += expired;
994
+ if (processedIterations < requestCount && Date.now() > scenarioDeadlineMs) {
995
+ const timedOutIterations = requestCount - processedIterations;
996
+ if (timeoutCountsAsFailure) {
997
+ failCount += timedOutIterations;
998
+ }
999
+ else {
1000
+ okCount += timedOutIterations;
1001
+ }
1002
1002
  }
1003
+ return { requestCount: processedIterations, okCount, failCount };
1003
1004
  }
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
- }
1005
+ finally {
1006
+ await Promise.allSettled([
1007
+ sourceAdapter.dispose?.(),
1008
+ destinationAdapter?.dispose?.(),
1009
+ correlationStore?.close?.()
1010
+ ].filter((value) => Boolean(value)));
1012
1011
  }
1013
- return { requestCount: processedIterations, okCount, failCount };
1014
1012
  }
1015
1013
  function validateTrackingConfiguration(tracking, sourceEndpoint, destinationEndpoint) {
1016
1014
  if (sourceEndpoint.gatherByField?.trim()) {
@@ -6626,6 +6626,7 @@ function mapRuntimeTrackingEndpointSpec(spec, useLoadStrikeTraceIdHeader = false
6626
6626
  nats: asTrackingRecord(pickTrackingValue(spec, "Nats", "nats")),
6627
6627
  redisStreams: asTrackingRecord(pickTrackingValue(spec, "RedisStreams", "redisStreams")),
6628
6628
  azureEventHubs: asTrackingRecord(pickTrackingValue(spec, "AzureEventHubs", "azureEventHubs")),
6629
+ sqs: asTrackingRecord(pickTrackingValue(spec, "Sqs", "sqs")),
6629
6630
  pushDiffusion: asTrackingRecord(pickTrackingValue(spec, "PushDiffusion", "pushDiffusion")),
6630
6631
  delegate: typeof delegateProduce === "function"
6631
6632
  || typeof delegateConsume === "function"