@loadstrike/loadstrike-sdk 1.0.10101 → 1.0.15201

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
@@ -1,6 +1,6 @@
1
1
  # LoadStrike for TypeScript and JavaScript
2
2
 
3
- LoadStrike is a TypeScript and JavaScript SDK for defining and running load, traffic-correlation, and reporting scenarios directly inside your application or test process.
3
+ LoadStrike for TypeScript and JavaScript lets you define, run, and report load scenarios directly from your application or test suite.
4
4
 
5
5
  ## Requirements
6
6
 
@@ -12,12 +12,12 @@ LoadStrike is a TypeScript and JavaScript SDK for defining and running load, tra
12
12
  npm install @loadstrike/loadstrike-sdk
13
13
  ```
14
14
 
15
- ## What It Provides
15
+ ## What You Can Do
16
16
 
17
- - Scenario, step, load-simulation, threshold, and metric primitives
18
- - Native in-process execution with structured run results
19
- - Local and distributed cluster execution helpers
20
- - HTML, TXT, CSV, and Markdown report generation
17
+ - Define scenarios, steps, load simulations, thresholds, and metrics in code
18
+ - Run workloads in-process and collect structured results
19
+ - Generate HTML, Markdown, TXT, and CSV reports
20
+ - Extend runs with correlation, distributed execution, and reporting sinks when needed
21
21
 
22
22
  ## Supported Transports
23
23
 
@@ -66,8 +66,8 @@ const result = await LoadStrikeRunner
66
66
  .run();
67
67
  ```
68
68
 
69
- A valid `RunnerKey` is required to execute live workloads.
69
+ Use a runner key from your LoadStrike portal account when executing live workloads.
70
70
 
71
- ## Documentation
71
+ ## Documentation And Examples
72
72
 
73
73
  https://loadstrike.com/documentation
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DistributedClusterAgent = exports.DistributedClusterCoordinator = exports.LocalClusterCoordinator = void 0;
3
+ exports.__loadstrikeTestExports = exports.DistributedClusterAgent = exports.DistributedClusterCoordinator = exports.LocalClusterCoordinator = void 0;
4
4
  exports.planAgentScenarioAssignments = planAgentScenarioAssignments;
5
5
  const node_crypto_1 = require("node:crypto");
6
6
  const transports_js_1 = require("./transports.js");
@@ -408,3 +408,20 @@ function booleanOrDefault(value, fallback) {
408
408
  async function sleep(ms) {
409
409
  await new Promise((resolve) => setTimeout(resolve, ms));
410
410
  }
411
+ exports.__loadstrikeTestExports = {
412
+ DistributedClusterAgent,
413
+ DistributedClusterCoordinator,
414
+ LocalClusterCoordinator,
415
+ arrayOrUndefined,
416
+ booleanOrDefault,
417
+ buildWeightedCycle,
418
+ convertRunResult,
419
+ numberOrDefault,
420
+ parseRunCommand,
421
+ parseRunResult,
422
+ planAgentScenarioAssignments,
423
+ recordOrUndefined,
424
+ sanitizeToken,
425
+ sleep,
426
+ stringOrDefault
427
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CrossPlatformTrackingRuntime = exports.TrackingFieldSelector = exports.RedisCorrelationStore = exports.InMemoryCorrelationStore = exports.CorrelationStoreConfiguration = exports.RedisCorrelationStoreOptions = exports.TrackingPayloadBuilder = void 0;
3
+ exports.__loadstrikeTestExports = exports.CrossPlatformTrackingRuntime = exports.TrackingFieldSelector = exports.RedisCorrelationStore = exports.InMemoryCorrelationStore = exports.CorrelationStoreConfiguration = exports.RedisCorrelationStoreOptions = exports.TrackingPayloadBuilder = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const redis_1 = require("redis");
6
6
  class TrackingPayloadBuilder {
@@ -559,11 +559,10 @@ class TrackingFieldSelector {
559
559
  }
560
560
  extract(payload) {
561
561
  if (this.kind === "header") {
562
- const headerName = this.path.toLowerCase();
563
562
  const headers = payload.headers ?? {};
564
563
  for (const [key, value] of Object.entries(headers)) {
565
564
  const parsed = String(value ?? "").trim();
566
- if (key.toLowerCase() === headerName && parsed) {
565
+ if (key === this.path && parsed) {
567
566
  return parsed;
568
567
  }
569
568
  }
@@ -604,6 +603,8 @@ function normalizeTrackingFieldLocation(location) {
604
603
  }
605
604
  class CrossPlatformTrackingRuntime {
606
605
  constructor(options) {
606
+ this.trackingIdDisplayByEventId = new Map();
607
+ this.gatherByDisplayByComparisonKey = new Map();
607
608
  this.producedCount = 0;
608
609
  this.consumedCount = 0;
609
610
  this.matchedCount = 0;
@@ -617,6 +618,8 @@ class CrossPlatformTrackingRuntime {
617
618
  this.gatherSelector = options.destinationGatherByField
618
619
  ? TrackingFieldSelector.parse(options.destinationGatherByField)
619
620
  : null;
621
+ this.trackingFieldValueCaseSensitive = options.trackingFieldValueCaseSensitive ?? true;
622
+ this.gatherByFieldValueCaseSensitive = options.gatherByFieldValueCaseSensitive ?? true;
620
623
  this.timeoutMs = Math.max(options.correlationTimeoutMs ?? 30000, 1);
621
624
  this.timeoutCountsAsFailure = options.timeoutCountsAsFailure ?? true;
622
625
  this.store = options.store ?? new InMemoryCorrelationStore();
@@ -639,13 +642,17 @@ class CrossPlatformTrackingRuntime {
639
642
  return result;
640
643
  }
641
644
  result.trackingId = trackingId;
642
- const sourceOccurrences = await this.store.incrementSourceOccurrences(trackingId);
645
+ const comparisonTrackingId = normalizeComparisonValue(trackingId, this.trackingFieldValueCaseSensitive);
646
+ const sourceOccurrences = await this.store.incrementSourceOccurrences(comparisonTrackingId);
643
647
  if (sourceOccurrences > 1) {
644
648
  this.duplicateSourceCount += 1;
645
649
  result.duplicateSource = true;
646
650
  }
647
- const registration = await this.store.registerSource(trackingId, nowMs);
651
+ const registration = await this.store.registerSource(comparisonTrackingId, nowMs);
648
652
  result.eventId = registration.eventId ?? "";
653
+ if (registration.eventId) {
654
+ this.trackingIdDisplayByEventId.set(registration.eventId, trackingId);
655
+ }
649
656
  if (registration.eventId) {
650
657
  await this.store.tryExpire(registration.eventId);
651
658
  }
@@ -686,12 +693,13 @@ class CrossPlatformTrackingRuntime {
686
693
  return result;
687
694
  }
688
695
  result.trackingId = trackingId;
689
- const destinationOccurrences = await this.store.incrementDestinationOccurrences(trackingId);
696
+ const comparisonTrackingId = normalizeComparisonValue(trackingId, this.trackingFieldValueCaseSensitive);
697
+ const destinationOccurrences = await this.store.incrementDestinationOccurrences(comparisonTrackingId);
690
698
  if (destinationOccurrences > 1) {
691
699
  this.duplicateDestinationCount += 1;
692
700
  result.duplicateDestination = true;
693
701
  }
694
- const source = await this.store.tryMatchDestination(trackingId, nowMs);
702
+ const source = await this.store.tryMatchDestination(comparisonTrackingId, nowMs);
695
703
  if (!source) {
696
704
  result.message = "Destination tracking id had no source match.";
697
705
  return result;
@@ -699,10 +707,12 @@ class CrossPlatformTrackingRuntime {
699
707
  this.matchedCount += 1;
700
708
  const latencyMs = Math.max(nowMs - (source.producedUtc ?? source.sourceTimestampUtc ?? nowMs), 0);
701
709
  this.totalLatencyMs += latencyMs;
710
+ const displayTrackingId = this.resolveTrackingIdDisplay(source.eventId, source.trackingId, true);
702
711
  const gatherKey = this.resolveGatherKey(payload);
703
712
  result.eventId = source.eventId ?? "";
704
713
  result.sourceTimestampUtc = source.sourceTimestampUtc ?? source.producedUtc ?? 0;
705
714
  result.destinationTimestampUtc = nowMs;
715
+ result.trackingId = displayTrackingId;
706
716
  result.gatherKey = gatherKey;
707
717
  result.matched = true;
708
718
  result.latencyMs = latencyMs;
@@ -712,7 +722,7 @@ class CrossPlatformTrackingRuntime {
712
722
  this.gathered.set(gatherKey, row);
713
723
  for (const plugin of this.plugins) {
714
724
  if (plugin.onMatched) {
715
- await plugin.onMatched(trackingId, source.payload, payload, latencyMs);
725
+ await plugin.onMatched(displayTrackingId, source.payload, payload, latencyMs, gatherKey);
716
726
  }
717
727
  }
718
728
  return result;
@@ -760,7 +770,9 @@ class CrossPlatformTrackingRuntime {
760
770
  expiredEntries.push(entry);
761
771
  }
762
772
  for (const entry of expiredEntries) {
773
+ const displayTrackingId = this.resolveTrackingIdDisplay(entry.eventId, entry.trackingId, true);
763
774
  this.timeoutCount += 1;
775
+ entry.trackingId = displayTrackingId;
764
776
  const gatherKey = this.resolveGatherKey(entry.payload);
765
777
  const row = this.gathered.get(gatherKey) ?? { total: 0, matched: 0, timedOut: 0 };
766
778
  row.total += 1;
@@ -768,7 +780,7 @@ class CrossPlatformTrackingRuntime {
768
780
  this.gathered.set(gatherKey, row);
769
781
  for (const plugin of this.plugins) {
770
782
  if (plugin.onTimeout) {
771
- await plugin.onTimeout(entry.trackingId, entry.payload);
783
+ await plugin.onTimeout(displayTrackingId, entry.payload);
772
784
  }
773
785
  }
774
786
  }
@@ -792,13 +804,42 @@ class CrossPlatformTrackingRuntime {
792
804
  if (!this.gatherSelector) {
793
805
  return "__ungrouped__";
794
806
  }
795
- return this.gatherSelector.extract(payload) || "__unknown__";
807
+ const gatherKey = this.gatherSelector.extract(payload);
808
+ if (!gatherKey) {
809
+ return "__unknown__";
810
+ }
811
+ if (this.gatherByFieldValueCaseSensitive) {
812
+ return gatherKey;
813
+ }
814
+ const comparisonKey = normalizeComparisonValue(gatherKey, false);
815
+ const existing = this.gatherByDisplayByComparisonKey.get(comparisonKey);
816
+ if (existing) {
817
+ return existing;
818
+ }
819
+ this.gatherByDisplayByComparisonKey.set(comparisonKey, gatherKey);
820
+ return gatherKey;
796
821
  }
797
822
  isTimeoutCountedAsFailure() {
798
823
  return this.timeoutCountsAsFailure;
799
824
  }
825
+ resolveTrackingIdDisplay(eventId, fallbackTrackingId, remove) {
826
+ if (!eventId?.trim()) {
827
+ return fallbackTrackingId;
828
+ }
829
+ if (remove) {
830
+ const displayTrackingId = this.trackingIdDisplayByEventId.get(eventId);
831
+ this.trackingIdDisplayByEventId.delete(eventId);
832
+ return displayTrackingId ?? fallbackTrackingId;
833
+ }
834
+ return this.trackingIdDisplayByEventId.get(eventId) ?? fallbackTrackingId;
835
+ }
800
836
  }
801
837
  exports.CrossPlatformTrackingRuntime = CrossPlatformTrackingRuntime;
838
+ function normalizeComparisonValue(value, caseSensitive) {
839
+ return caseSensitive
840
+ ? value
841
+ : value.toUpperCase();
842
+ }
802
843
  function normalizeJsonBody(body) {
803
844
  if (body instanceof Uint8Array) {
804
845
  return normalizeJsonBody(trackingBodyToUtf8(body));
@@ -917,7 +958,9 @@ function uniqueStringList(values) {
917
958
  return [...new Set(values.filter((value) => value.trim().length > 0))];
918
959
  }
919
960
  function createRedisStoreClient(connectionString, database) {
920
- const client = (0, redis_1.createClient)({
961
+ const createClientOverride = globalThis
962
+ .__wave15CreateRedisClient;
963
+ const client = (typeof createClientOverride === "function" ? createClientOverride : redis_1.createClient)({
921
964
  url: connectionString,
922
965
  database: database >= 0 ? database : undefined
923
966
  });
@@ -1007,3 +1050,18 @@ function readJsonPathSegment(current, segment) {
1007
1050
  }
1008
1051
  return undefined;
1009
1052
  }
1053
+ exports.__loadstrikeTestExports = {
1054
+ CorrelationStoreConfiguration,
1055
+ InMemoryCorrelationStore,
1056
+ RedisCorrelationStore,
1057
+ RedisCorrelationStoreOptions,
1058
+ TrackingFieldSelector,
1059
+ createRedisStoreClient,
1060
+ hydrateCorrelationEntry,
1061
+ normalizeCorrelationEntry,
1062
+ parseStringList,
1063
+ readJsonPathSegment,
1064
+ sanitizeLooseJson,
1065
+ tryParseObject,
1066
+ uniqueStringList
1067
+ };
package/dist/cjs/index.js CHANGED
@@ -1,17 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DatadogReportingSinkOptions = exports.DatadogReportingSink = exports.ConsoleReportingSink = exports.CompositeReportingSink = exports.planAgentScenarioAssignments = exports.LocalClusterCoordinator = exports.DistributedClusterCoordinator = exports.DistributedClusterAgent = 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.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.LoadStrikeSimulation = exports.LoadStrikeScenario = exports.LoadStrikeRunner = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeResponse = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = exports.LoadStrikeReportData = exports.LoadStrikePluginDataTable = exports.LoadStrikePluginData = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeLocalClient = exports.DEFAULT_LICENSING_API_BASE_URL = void 0;
4
- exports.TimescaleDbReportingSinkOptions = exports.TimescaleDbReportingSink = exports.SplunkReportingSinkOptions = exports.SplunkReportingSink = exports.OtelCollectorReportingSinkOptions = exports.OtelCollectorReportingSink = exports.MemoryReportingSink = exports.InfluxDbReportingSinkOptions = exports.InfluxDbReportingSink = exports.GrafanaLokiReportingSinkOptions = exports.GrafanaLokiReportingSink = void 0;
5
- var local_js_1 = require("./local.js");
6
- Object.defineProperty(exports, "DEFAULT_LICENSING_API_BASE_URL", { enumerable: true, get: function () { return local_js_1.DEFAULT_LICENSING_API_BASE_URL; } });
7
- Object.defineProperty(exports, "LoadStrikeLocalClient", { enumerable: true, get: function () { return local_js_1.LoadStrikeLocalClient; } });
3
+ exports.TimescaleDbReportingSink = exports.SplunkReportingSinkOptions = exports.SplunkReportingSink = exports.OtelCollectorReportingSinkOptions = exports.OtelCollectorReportingSink = exports.InfluxDbReportingSinkOptions = 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.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.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 = void 0;
4
+ exports.TimescaleDbReportingSinkOptions = void 0;
8
5
  var runtime_js_1 = require("./runtime.js");
9
6
  Object.defineProperty(exports, "CrossPlatformScenarioConfigurator", { enumerable: true, get: function () { return runtime_js_1.CrossPlatformScenarioConfigurator; } });
10
7
  Object.defineProperty(exports, "ScenarioTrackingExtensions", { enumerable: true, get: function () { return runtime_js_1.ScenarioTrackingExtensions; } });
11
8
  Object.defineProperty(exports, "LoadStrikeContext", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeContext; } });
12
9
  Object.defineProperty(exports, "LoadStrikePluginData", { enumerable: true, get: function () { return runtime_js_1.LoadStrikePluginData; } });
13
10
  Object.defineProperty(exports, "LoadStrikePluginDataTable", { enumerable: true, get: function () { return runtime_js_1.LoadStrikePluginDataTable; } });
14
- Object.defineProperty(exports, "LoadStrikeReportData", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeReportData; } });
15
11
  Object.defineProperty(exports, "LoadStrikeNodeType", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeNodeType; } });
16
12
  Object.defineProperty(exports, "LoadStrikeReportFormat", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeReportFormat; } });
17
13
  Object.defineProperty(exports, "LoadStrikeResponse", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeResponse; } });
@@ -48,21 +44,13 @@ Object.defineProperty(exports, "DelegateStreamEndpointDefinition", { enumerable:
48
44
  Object.defineProperty(exports, "PushDiffusionEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.PushDiffusionEndpointDefinition; } });
49
45
  Object.defineProperty(exports, "HttpOAuth2ClientCredentialsOptions", { enumerable: true, get: function () { return transports_js_1.HttpOAuth2ClientCredentialsOptions; } });
50
46
  Object.defineProperty(exports, "HttpAuthOptions", { enumerable: true, get: function () { return transports_js_1.HttpAuthOptions; } });
51
- var cluster_js_1 = require("./cluster.js");
52
- Object.defineProperty(exports, "DistributedClusterAgent", { enumerable: true, get: function () { return cluster_js_1.DistributedClusterAgent; } });
53
- Object.defineProperty(exports, "DistributedClusterCoordinator", { enumerable: true, get: function () { return cluster_js_1.DistributedClusterCoordinator; } });
54
- Object.defineProperty(exports, "LocalClusterCoordinator", { enumerable: true, get: function () { return cluster_js_1.LocalClusterCoordinator; } });
55
- Object.defineProperty(exports, "planAgentScenarioAssignments", { enumerable: true, get: function () { return cluster_js_1.planAgentScenarioAssignments; } });
56
47
  var sinks_js_1 = require("./sinks.js");
57
- Object.defineProperty(exports, "CompositeReportingSink", { enumerable: true, get: function () { return sinks_js_1.CompositeReportingSink; } });
58
- Object.defineProperty(exports, "ConsoleReportingSink", { enumerable: true, get: function () { return sinks_js_1.ConsoleReportingSink; } });
59
48
  Object.defineProperty(exports, "DatadogReportingSink", { enumerable: true, get: function () { return sinks_js_1.DatadogReportingSink; } });
60
49
  Object.defineProperty(exports, "DatadogReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.DatadogReportingSinkOptions; } });
61
50
  Object.defineProperty(exports, "GrafanaLokiReportingSink", { enumerable: true, get: function () { return sinks_js_1.GrafanaLokiReportingSink; } });
62
51
  Object.defineProperty(exports, "GrafanaLokiReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.GrafanaLokiReportingSinkOptions; } });
63
52
  Object.defineProperty(exports, "InfluxDbReportingSink", { enumerable: true, get: function () { return sinks_js_1.InfluxDbReportingSink; } });
64
53
  Object.defineProperty(exports, "InfluxDbReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.InfluxDbReportingSinkOptions; } });
65
- Object.defineProperty(exports, "MemoryReportingSink", { enumerable: true, get: function () { return sinks_js_1.MemoryReportingSink; } });
66
54
  Object.defineProperty(exports, "OtelCollectorReportingSink", { enumerable: true, get: function () { return sinks_js_1.OtelCollectorReportingSink; } });
67
55
  Object.defineProperty(exports, "OtelCollectorReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.OtelCollectorReportingSinkOptions; } });
68
56
  Object.defineProperty(exports, "SplunkReportingSink", { enumerable: true, get: function () { return sinks_js_1.SplunkReportingSink; } });
package/dist/cjs/local.js CHANGED
@@ -36,14 +36,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.LoadStrikeLocalClient = exports.DEFAULT_LICENSING_API_BASE_URL = void 0;
39
+ exports.__loadstrikeTestExports = exports.LoadStrikeLocalClient = void 0;
40
40
  const node_os_1 = __importDefault(require("node:os"));
41
41
  const fs = __importStar(require("node:fs"));
42
42
  const childProcess = __importStar(require("node:child_process"));
43
43
  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
- exports.DEFAULT_LICENSING_API_BASE_URL = "https://licensing.loadstrike.com";
46
+ const DEFAULT_LICENSING_API_BASE_URL = "https://licensing.loadstrike.com";
47
+ let developmentLicensingApiBaseUrlOverride;
47
48
  const BUILT_IN_WORKER_PLUGIN_NAMES = new Set([
48
49
  "loadstrike failed responses",
49
50
  "loadstrike correlation"
@@ -87,7 +88,7 @@ class LoadStrikeLocalClient {
87
88
  constructor(options = {}) {
88
89
  this.signingKeyCache = new Map();
89
90
  assertNoDisableLicenseEnforcementOption(options, "LoadStrikeLocalClient");
90
- this.licensingApiBaseUrl = normalizeLicensingApiBaseUrl(options.licensingApiBaseUrl);
91
+ this.licensingApiBaseUrl = resolveLicensingApiBaseUrl();
91
92
  this.licenseValidationTimeoutMs = normalizeTimeoutMs(options.licenseValidationTimeoutMs);
92
93
  }
93
94
  async run(request) {
@@ -177,9 +178,10 @@ class LoadStrikeLocalClient {
177
178
  const timer = setTimeout(() => controller.abort(), this.licenseValidationTimeoutMs);
178
179
  try {
179
180
  const { response, json } = await this.postLicensingRequest("/api/v1/licenses/validate", payload, controller.signal);
180
- if (!response.ok || json.IsValid !== true) {
181
- const denialCode = stringOrDefault(json.DenialCode, "unknown_denial");
182
- const details = stringOrDefault(json.Message, "No additional details provided.");
181
+ const isValid = pickValue(json, "IsValid", "isValid");
182
+ if (!response.ok || isValid !== true) {
183
+ const denialCode = stringOrDefault(pickValue(json, "DenialCode", "denialCode"), "unknown_denial");
184
+ const details = stringOrDefault(pickValue(json, "Message", "message"), "No additional details provided.");
183
185
  throw new Error(`Runner key validation denied for '${runnerKey}'. Reason: ${denialCode}. ${details}`);
184
186
  }
185
187
  const runToken = stringOrDefault(pickValue(json, "RunToken", "SignedRunToken", "Token", "runToken"), "").trim();
@@ -257,7 +259,9 @@ class LoadStrikeLocalClient {
257
259
  }
258
260
  async getSigningKey(keyId, algorithm) {
259
261
  const nowMs = Date.now();
260
- const cacheKey = this.licensingApiBaseUrl.toLowerCase();
262
+ const normalizedKeyId = stringOrDefault(keyId, "default").trim() || "default";
263
+ const normalizedAlgorithm = stringOrDefault(algorithm, "RS256").toUpperCase();
264
+ const cacheKey = `${this.licensingApiBaseUrl.toLowerCase()}|${normalizedAlgorithm}:${normalizedKeyId}`;
261
265
  const cached = this.signingKeyCache.get(cacheKey);
262
266
  if (cached && cached.expiresAtUtc > nowMs) {
263
267
  return cached;
@@ -265,17 +269,21 @@ class LoadStrikeLocalClient {
265
269
  const controller = new AbortController();
266
270
  const timer = setTimeout(() => controller.abort(), this.licenseValidationTimeoutMs);
267
271
  try {
268
- const { response, json } = await this.getLicensingRequest("/api/v1/licenses/signing-public-key", controller.signal);
272
+ const { response, json } = await this.getLicensingRequest(`/api/v1/licenses/signing-public-key?kid=${encodeURIComponent(normalizedKeyId)}`, controller.signal);
269
273
  if (!response.ok) {
270
274
  throw new Error(`Failed to resolve licensing signing key. status=${response.status}`);
271
275
  }
272
- const resolvedAlgorithm = stringOrDefault(pickValue(json, "Algorithm", "alg"), algorithm).toUpperCase();
273
- const publicKeyPem = stringOrDefault(pickValue(json, "PublicKeyPem", "PublicKey", "Pem"), "");
276
+ const resolvedAlgorithm = stringOrDefault(pickValue(json, "Algorithm", "algorithm", "alg"), normalizedAlgorithm).toUpperCase();
277
+ const publicKeyPem = normalizePemText(pickValue(json, "PublicKeyPem", "publicKeyPem", "PublicKey", "publicKey", "Pem", "pem"));
274
278
  if (!publicKeyPem.trim()) {
275
279
  throw new Error("Runner key validation failed: signing key response is empty.");
276
280
  }
281
+ const resolvedKeyId = stringOrDefault(pickValue(json, "KeyId", "keyId", "Kid", "kid"), normalizedKeyId).trim() || normalizedKeyId;
282
+ if (resolvedKeyId !== normalizedKeyId) {
283
+ throw new Error("Runner key validation failed: signing key response key id does not match requested key id.");
284
+ }
277
285
  const keyRecord = {
278
- keyId: stringOrDefault(pickValue(json, "KeyId", "Kid", "kid"), keyId),
286
+ keyId: resolvedKeyId,
279
287
  algorithm: resolvedAlgorithm,
280
288
  issuer: stringOrDefault(pickValue(json, "Issuer", "issuer"), ""),
281
289
  audience: stringOrDefault(pickValue(json, "Audience", "audience"), ""),
@@ -394,7 +402,13 @@ function sanitizeRequest(request) {
394
402
  }
395
403
  function normalizeLicensingApiBaseUrl(value) {
396
404
  const normalized = (value ?? "").trim();
397
- return normalized || exports.DEFAULT_LICENSING_API_BASE_URL;
405
+ return normalized || DEFAULT_LICENSING_API_BASE_URL;
406
+ }
407
+ function resolveLicensingApiBaseUrl() {
408
+ return normalizeLicensingApiBaseUrl(process.env.LOADSTRIKE_LICENSING_API_BASE_URL ?? developmentLicensingApiBaseUrlOverride);
409
+ }
410
+ function setDevelopmentLicensingApiBaseUrlOverride(value) {
411
+ developmentLicensingApiBaseUrlOverride = value;
398
412
  }
399
413
  function normalizeTimeoutMs(value) {
400
414
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
@@ -451,14 +465,8 @@ function collectRequestedFeatures(request) {
451
465
  if (hasCustomSink) {
452
466
  features.add("extensions.reporting_sinks.custom");
453
467
  }
454
- if (pickValue(context, "ReportFinalizer", "reportFinalizer") != null) {
455
- features.add("policy.report_finalizer_and_threshold_controls");
456
- }
457
468
  for (const scenarioValue of scenarios) {
458
469
  const scenario = asRecord(scenarioValue);
459
- for (const feature of normalizeStringArray(pickValue(scenario, "LicenseFeatures", "licenseFeatures"))) {
460
- features.add(feature);
461
- }
462
470
  if (asList(scenario.Thresholds).length > 0) {
463
471
  features.add("policy.report_finalizer_and_threshold_controls");
464
472
  }
@@ -748,6 +756,8 @@ async function evaluateScenarioOutcome(scenario, requestCount, context) {
748
756
  sourceTrackingField: sourceEndpoint.trackingField,
749
757
  destinationTrackingField: destinationEndpoint?.trackingField,
750
758
  destinationGatherByField: destinationEndpoint?.gatherByField,
759
+ trackingFieldValueCaseSensitive: toBoolean(pickValue(tracking, "TrackingFieldValueCaseSensitive", "trackingFieldValueCaseSensitive"), true),
760
+ gatherByFieldValueCaseSensitive: toBoolean(pickValue(tracking, "GatherByFieldValueCaseSensitive", "gatherByFieldValueCaseSensitive"), true),
751
761
  correlationTimeoutMs: timeoutMs,
752
762
  timeoutCountsAsFailure,
753
763
  store: correlationStore ?? undefined
@@ -1128,11 +1138,11 @@ function normalizePayload(payload, endpoint, index) {
1128
1138
  function readTrackingId(payload, selector) {
1129
1139
  const normalized = selector.trim().toLowerCase();
1130
1140
  if (normalized.startsWith("header:")) {
1131
- const expected = selector.slice("header:".length).trim().toLowerCase();
1141
+ const expected = selector.slice("header:".length).trim();
1132
1142
  const headers = payload.headers ?? {};
1133
1143
  for (const [key, value] of Object.entries(headers)) {
1134
1144
  const normalizedValue = String(value ?? "").trim();
1135
- if (key.toLowerCase() === expected && normalizedValue) {
1145
+ if (key === expected && normalizedValue) {
1136
1146
  return normalizedValue;
1137
1147
  }
1138
1148
  }
@@ -1573,8 +1583,8 @@ function isRuntimeReportingSink(value) {
1573
1583
  "SaveRealtimeStats",
1574
1584
  "saveRealtimeMetrics",
1575
1585
  "SaveRealtimeMetrics",
1576
- "saveFinalStats",
1577
- "SaveFinalStats",
1586
+ "saveRunResult",
1587
+ "SaveRunResult",
1578
1588
  "stop",
1579
1589
  "Stop"
1580
1590
  ].some((name) => typeof record[name] === "function");
@@ -1600,6 +1610,20 @@ function stringOrDefault(value, fallback) {
1600
1610
  }
1601
1611
  return String(value);
1602
1612
  }
1613
+ function normalizePemText(value) {
1614
+ let text = stringOrDefault(value, "").trim();
1615
+ if (!text) {
1616
+ return "";
1617
+ }
1618
+ if (text.length >= 2 && text.startsWith("\"") && text.endsWith("\"")) {
1619
+ text = text.slice(1, -1).trim();
1620
+ }
1621
+ text = text.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\r/g, "\r").trim();
1622
+ if (text.length >= 2 && text.startsWith("\"") && text.endsWith("\"")) {
1623
+ text = text.slice(1, -1).trim();
1624
+ }
1625
+ return text;
1626
+ }
1603
1627
  function asInt(value) {
1604
1628
  if (typeof value === "number" && Number.isFinite(value)) {
1605
1629
  return Math.trunc(value);
@@ -1882,3 +1906,64 @@ function resolveTimeoutSeconds(context, secondsKey, secondsAlias, millisecondsKe
1882
1906
  function asList(value) {
1883
1907
  return Array.isArray(value) ? value : [];
1884
1908
  }
1909
+ exports.__loadstrikeTestExports = {
1910
+ addIfNotBlank,
1911
+ asNullableInt,
1912
+ asNumber,
1913
+ buildThresholdExpression,
1914
+ collectClaimStrings,
1915
+ collectRequestedFeatures,
1916
+ computeScenarioRequestCount,
1917
+ countCustomWorkerPlugins,
1918
+ countTargetedScenarios,
1919
+ decodeBase64Url,
1920
+ decodeJwtJsonSegment,
1921
+ deviceHash,
1922
+ enforceEntitlementClaims,
1923
+ enforceJwtLifetime,
1924
+ enforceRuntimePolicyClaims,
1925
+ evaluateScenarioOutcome,
1926
+ evaluateScenarioThresholds,
1927
+ fileContains,
1928
+ hasLinuxDesktopSession,
1929
+ hasTruthyEnv,
1930
+ inferLegacyHttpResponseSourceValue,
1931
+ isContainerHost,
1932
+ isLinuxLocalHost,
1933
+ isLocalHost,
1934
+ isRuntimeReportingSink,
1935
+ mapCorrelationStore,
1936
+ mapEnvironmentClassification,
1937
+ mapEndpointSpec,
1938
+ normalizeEndpointPayloadValue,
1939
+ normalizeLicensingApiBaseUrl,
1940
+ resolveLicensingApiBaseUrl,
1941
+ normalizeNodeType,
1942
+ normalizePayload,
1943
+ normalizeStringArray,
1944
+ normalizeTimeoutMs,
1945
+ parseBodyAsObject,
1946
+ parseJwt,
1947
+ readEpochClaim,
1948
+ readLinuxMachineId,
1949
+ readMacPlatformUuid,
1950
+ readOptionalTrackingSelectorValue,
1951
+ readRedisCorrelationStoreOptions,
1952
+ readTrackingId,
1953
+ readWindowsMachineGuid,
1954
+ resolveRuntimePolicy,
1955
+ resolveTimeoutSeconds,
1956
+ sanitizeLooseJson,
1957
+ sanitizeRequest,
1958
+ setJsonPathValue,
1959
+ setDevelopmentLicensingApiBaseUrlOverride,
1960
+ sleep,
1961
+ stringOrDefault,
1962
+ toBoolean,
1963
+ toContractNodeType,
1964
+ toStringMap,
1965
+ tryParseJson,
1966
+ validateRedisCorrelationStoreConfiguration,
1967
+ validateTrackingConfiguration,
1968
+ verifyJwtSignature
1969
+ };
@@ -1,17 +1,14 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__loadstrikeTestExports = void 0;
6
4
  exports.buildDotnetTxtReport = buildDotnetTxtReport;
7
5
  exports.buildDotnetCsvReport = buildDotnetCsvReport;
8
6
  exports.buildDotnetMarkdownReport = buildDotnetMarkdownReport;
9
7
  exports.buildDotnetHtmlReport = buildDotnetHtmlReport;
10
8
  const node_fs_1 = require("node:fs");
11
- const node_os_1 = __importDefault(require("node:os"));
12
9
  const node_path_1 = require("node:path");
13
10
  const node_url_1 = require("node:url");
14
- const REPORT_EOL = node_os_1.default.EOL;
11
+ const REPORT_EOL = "\n";
15
12
  const REPORT_FALLBACK_SVG = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'><defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'><stop offset='0' stop-color='#64b5ff'/><stop offset='1' stop-color='#2f66db'/></linearGradient></defs><rect width='128' height='128' rx='24' fill='#081325'/><path d='M24 94L56 24l16 34 14-22 18 58h-16l-7-23-9 13-10-21-14 31H24z' fill='url(#g)'/></svg>";
16
13
  const REPORT_LOGO_CACHE = new Map();
17
14
  function resolveReportModuleDir() {
@@ -1265,3 +1262,20 @@ if(btns.length>0){show(btns[0].dataset.tab);}renderAllCharts();initPanePan();win
1265
1262
  .split("__CHART_DATA__").join(chartDataJson)
1266
1263
  .concat(REPORT_EOL);
1267
1264
  }
1265
+ exports.__loadstrikeTestExports = {
1266
+ buildDotnetFailedResponseContent,
1267
+ buildDotnetFailedResponseHtml,
1268
+ buildDotnetGroupedCorrelationSummaryHtml,
1269
+ buildDotnetHtmlTabs,
1270
+ buildDotnetMetricHtml,
1271
+ buildDotnetPluginHints,
1272
+ buildDotnetScenarioHtml,
1273
+ buildDotnetScenarioMeasurementHtml,
1274
+ buildDotnetStatusCodeHtml,
1275
+ buildDotnetStepHtml,
1276
+ buildDotnetStepMeasurementHtml,
1277
+ buildDotnetThresholdHtml,
1278
+ buildDotnetUngroupedCorrelationSummaryHtml,
1279
+ buildUngroupedCorrelationChartPayload,
1280
+ classifyStatusCodeBucket
1281
+ };