@loadstrike/loadstrike-sdk 1.0.31601 → 1.0.32601
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/dist/cjs/internal/prometheus-remote-write.js +37 -0
- package/dist/cjs/internal/reporting-sink-http-error.js +17 -0
- package/dist/cjs/internal/vendor-metric-payloads.js +390 -0
- package/dist/cjs/iteration-observations.js +24 -8
- package/dist/cjs/local.js +48 -63
- package/dist/cjs/reporting-containment.js +242 -0
- package/dist/cjs/runtime.js +83 -3
- package/dist/cjs/sinks.js +1337 -38
- package/dist/cjs/transports.js +1339 -151
- package/dist/esm/internal/prometheus-remote-write.js +31 -0
- package/dist/esm/internal/reporting-sink-http-error.js +13 -0
- package/dist/esm/internal/vendor-metric-payloads.js +382 -0
- package/dist/esm/iteration-observations.js +24 -8
- package/dist/esm/local.js +49 -64
- package/dist/esm/reporting-containment.js +238 -0
- package/dist/esm/runtime.js +85 -5
- package/dist/esm/sinks.js +1334 -35
- package/dist/esm/transports.js +1335 -151
- package/dist/types/contracts.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/internal/prometheus-remote-write.d.ts +2 -0
- package/dist/types/internal/reporting-sink-http-error.d.ts +6 -0
- package/dist/types/internal/vendor-metric-payloads.d.ts +48 -0
- package/dist/types/local.d.ts +0 -6
- package/dist/types/reporting-containment.d.ts +2 -0
- package/dist/types/runtime.d.ts +1 -0
- package/dist/types/sinks.d.ts +134 -17
- package/dist/types/transports.d.ts +2 -0
- package/package.json +9 -3
- package/dist/cjs/internal-build.js +0 -4
- package/dist/esm/internal-build.js +0 -1
- package/dist/types/internal-build.d.ts +0 -1
package/dist/cjs/local.js
CHANGED
|
@@ -55,9 +55,8 @@ const childProcess = __importStar(require("node:child_process"));
|
|
|
55
55
|
const node_crypto_1 = require("node:crypto");
|
|
56
56
|
const correlation_js_1 = require("./correlation.js");
|
|
57
57
|
const transports_js_1 = require("./transports.js");
|
|
58
|
-
const
|
|
58
|
+
const reporting_containment_js_1 = require("./reporting-containment.js");
|
|
59
59
|
const DEFAULT_LICENSING_API_BASE_URL = "https://licensing.loadstrike.com";
|
|
60
|
-
let developmentLicensingApiBaseUrlOverride;
|
|
61
60
|
const BUILT_IN_WORKER_PLUGIN_NAMES = new Set([
|
|
62
61
|
"loadstrike failed responses",
|
|
63
62
|
"loadstrike correlation"
|
|
@@ -111,7 +110,7 @@ class LoadStrikeLocalClient {
|
|
|
111
110
|
_LoadStrikeLocalClient_signingKeyCache.set(this, new Map());
|
|
112
111
|
_LoadStrikeLocalClient_heartbeatDrains.set(this, new WeakMap());
|
|
113
112
|
assertNoDisableLicenseEnforcementOption(options, "LoadStrikeLocalClient");
|
|
114
|
-
__classPrivateFieldSet(this, _LoadStrikeLocalClient_licensingApiBaseUrl,
|
|
113
|
+
__classPrivateFieldSet(this, _LoadStrikeLocalClient_licensingApiBaseUrl, DEFAULT_LICENSING_API_BASE_URL, "f");
|
|
115
114
|
this.licenseValidationTimeoutMs = normalizeTimeoutMs(options.licenseValidationTimeoutMs);
|
|
116
115
|
}
|
|
117
116
|
portalReportingIngestUrl() {
|
|
@@ -119,6 +118,7 @@ class LoadStrikeLocalClient {
|
|
|
119
118
|
}
|
|
120
119
|
async run(request) {
|
|
121
120
|
const sanitized = sanitizeRequest(request);
|
|
121
|
+
validateContainmentRequest(sanitized);
|
|
122
122
|
const licenseSession = await this.acquireLicenseLease(sanitized);
|
|
123
123
|
try {
|
|
124
124
|
const createdUtc = new Date();
|
|
@@ -574,66 +574,27 @@ function sanitizeRequest(request) {
|
|
|
574
574
|
RunArgs: runArgs
|
|
575
575
|
};
|
|
576
576
|
}
|
|
577
|
-
function
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const normalized = normalizeLicensingApiBaseUrl(value);
|
|
588
|
-
if (!isLoopbackHttpBaseUrl(normalized)) {
|
|
589
|
-
throw new TypeError("Internal development licensing API overrides must use a loopback http URL.");
|
|
577
|
+
function validateContainmentRequest(request) {
|
|
578
|
+
const scenarios = Array.isArray(request.Scenarios) ? request.Scenarios : [];
|
|
579
|
+
for (const scenarioValue of scenarios) {
|
|
580
|
+
const scenario = asRecord(scenarioValue);
|
|
581
|
+
const tracking = asRecord(pickValue(scenario, "Tracking", "tracking"));
|
|
582
|
+
for (const field of ["Source", "Destination"]) {
|
|
583
|
+
const endpoint = asRecord(pickValue(tracking, field, field.toLowerCase()));
|
|
584
|
+
if (Object.keys(endpoint).length > 0) {
|
|
585
|
+
(0, transports_js_1.validateNativeEndpointExecutionSupport)(endpoint);
|
|
586
|
+
}
|
|
590
587
|
}
|
|
591
|
-
developmentLicensingApiBaseUrlOverride = normalized;
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
if (value != null) {
|
|
595
|
-
assertInternalTestHarnessLicensingOverride();
|
|
596
|
-
}
|
|
597
|
-
developmentLicensingApiBaseUrlOverride = undefined;
|
|
598
|
-
}
|
|
599
|
-
function assertInternalTestHarnessLicensingOverride() {
|
|
600
|
-
if (isInternalTestHarnessCall()) {
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
throw new TypeError("Internal development licensing API override is available only to LoadStrike SDK tests.");
|
|
604
|
-
}
|
|
605
|
-
function isInternalTestHarnessCall() {
|
|
606
|
-
const stack = String(new Error().stack ?? "").replace(/\\/g, "/").toLowerCase();
|
|
607
|
-
return stack.includes("/sdk/ts/tests/");
|
|
608
|
-
}
|
|
609
|
-
function isLoopbackHttpBaseUrl(value) {
|
|
610
|
-
let parsed;
|
|
611
|
-
try {
|
|
612
|
-
parsed = new URL(value);
|
|
613
|
-
}
|
|
614
|
-
catch {
|
|
615
|
-
return false;
|
|
616
|
-
}
|
|
617
|
-
if (parsed.protocol !== "http:") {
|
|
618
|
-
return false;
|
|
619
588
|
}
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
const envToken = String(process.env.LOADSTRIKE_INTERNAL_BLACKBOX_API_BASE_URL_TOKEN ?? "").trim();
|
|
629
|
-
if (!envToken || envToken !== compiledToken) {
|
|
630
|
-
return undefined;
|
|
631
|
-
}
|
|
632
|
-
const candidate = normalizeLicensingApiBaseUrl(process.env.LOADSTRIKE_INTERNAL_BLACKBOX_API_BASE_URL);
|
|
633
|
-
if (!isLoopbackHttpBaseUrl(candidate)) {
|
|
634
|
-
return undefined;
|
|
589
|
+
const context = asRecord(request.Context);
|
|
590
|
+
const reportingSinks = asList(pickValue(context, "ReportingSinks", "reportingSinks"));
|
|
591
|
+
for (const [key, value] of Object.entries(context)) {
|
|
592
|
+
const normalizedKey = key.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
593
|
+
if (normalizedKey === "infraconfig") {
|
|
594
|
+
(0, reporting_containment_js_1.assertNoUnsupportedReportingInfraConfig)(value, reportingSinks);
|
|
595
|
+
}
|
|
635
596
|
}
|
|
636
|
-
|
|
597
|
+
(0, reporting_containment_js_1.assertNoUnsupportedReportingSinkGraph)(reportingSinks);
|
|
637
598
|
}
|
|
638
599
|
function normalizeTimeoutMs(value) {
|
|
639
600
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
@@ -1341,6 +1302,20 @@ function mapEndpointSpec(spec, useLoadStrikeTraceIdHeader = false) {
|
|
|
1341
1302
|
redisStreams: asRecord(pickValue(spec, "RedisStreams", "redisStreams")),
|
|
1342
1303
|
azureEventHubs: asRecord(pickValue(spec, "AzureEventHubs", "azureEventHubs")),
|
|
1343
1304
|
pushDiffusion: asRecord(pickValue(spec, "PushDiffusion", "pushDiffusion")),
|
|
1305
|
+
grpc: mapEndpointProtocolOptions(spec, ["Grpc", "grpc"], [
|
|
1306
|
+
"Target", "target", "ServiceName", "serviceName", "MethodName", "methodName",
|
|
1307
|
+
"MethodType", "methodType", "Deadline", "deadline", "DeadlineSeconds", "deadlineSeconds",
|
|
1308
|
+
"DeadlineMs", "deadlineMs", "Metadata", "metadata", "Produce", "produce", "Consume", "consume",
|
|
1309
|
+
"ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync", "ConnectionMetadata", "connectionMetadata",
|
|
1310
|
+
"NativeClient", "nativeClient"
|
|
1311
|
+
]),
|
|
1312
|
+
webSocket: mapEndpointProtocolOptions(spec, ["WebSocket", "webSocket"], [
|
|
1313
|
+
"Url", "url", "Subprotocols", "subprotocols", "ConnectTimeout", "connectTimeout",
|
|
1314
|
+
"ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeoutMs", "connectTimeoutMs",
|
|
1315
|
+
"CloseTimeout", "closeTimeout", "CloseTimeoutSeconds", "closeTimeoutSeconds", "CloseTimeoutMs", "closeTimeoutMs",
|
|
1316
|
+
"Produce", "produce", "Consume", "consume", "ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync",
|
|
1317
|
+
"ConnectionMetadata", "connectionMetadata", "NativeClient", "nativeClient"
|
|
1318
|
+
]),
|
|
1344
1319
|
delegate: (typeof delegateProduce === "function"
|
|
1345
1320
|
|| typeof delegateConsume === "function"
|
|
1346
1321
|
|| typeof delegateProduceAsync === "function"
|
|
@@ -1363,6 +1338,19 @@ function mapEndpointSpec(spec, useLoadStrikeTraceIdHeader = false) {
|
|
|
1363
1338
|
: undefined
|
|
1364
1339
|
};
|
|
1365
1340
|
}
|
|
1341
|
+
function mapEndpointProtocolOptions(spec, nestedKeys, flatKeys) {
|
|
1342
|
+
const nested = asRecord(pickValue(spec, ...nestedKeys));
|
|
1343
|
+
if (Object.keys(nested).length > 0) {
|
|
1344
|
+
return nested;
|
|
1345
|
+
}
|
|
1346
|
+
const options = {};
|
|
1347
|
+
for (const key of flatKeys) {
|
|
1348
|
+
if (Object.prototype.hasOwnProperty.call(spec, key)) {
|
|
1349
|
+
options[key] = spec[key];
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
return options;
|
|
1353
|
+
}
|
|
1366
1354
|
function normalizePayload(payload, endpoint, index) {
|
|
1367
1355
|
const resolvedBody = payload?.body ?? endpoint.messagePayload;
|
|
1368
1356
|
const normalized = {
|
|
@@ -2319,8 +2307,6 @@ exports.__loadstrikeTestExports = {
|
|
|
2319
2307
|
mapEnvironmentClassification,
|
|
2320
2308
|
mapEndpointSpec,
|
|
2321
2309
|
normalizeEndpointPayloadValue,
|
|
2322
|
-
normalizeLicensingApiBaseUrl,
|
|
2323
|
-
resolveLicensingApiBaseUrl,
|
|
2324
2310
|
normalizeNodeType,
|
|
2325
2311
|
normalizePayload,
|
|
2326
2312
|
normalizeStringArray,
|
|
@@ -2339,7 +2325,6 @@ exports.__loadstrikeTestExports = {
|
|
|
2339
2325
|
sanitizeLooseJson,
|
|
2340
2326
|
sanitizeRequest,
|
|
2341
2327
|
setJsonPathValue,
|
|
2342
|
-
setDevelopmentLicensingApiBaseUrlOverride,
|
|
2343
2328
|
sleep,
|
|
2344
2329
|
stringOrDefault,
|
|
2345
2330
|
toBoolean,
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertNoUnsupportedReportingInfraConfig = assertNoUnsupportedReportingInfraConfig;
|
|
4
|
+
exports.assertNoUnsupportedReportingSinkGraph = assertNoUnsupportedReportingSinkGraph;
|
|
5
|
+
const UNBOUND_EXPANDED_SECTIONS = [
|
|
6
|
+
["PrometheusRemoteWrite", "PrometheusRemoteWriteReportingSink"],
|
|
7
|
+
["CloudWatch", "CloudWatchReportingSink"],
|
|
8
|
+
["Dynatrace", "DynatraceReportingSink"],
|
|
9
|
+
["NewRelic", "NewRelicReportingSink"],
|
|
10
|
+
["Elasticsearch", "ElasticsearchReportingSink"],
|
|
11
|
+
["OpenSearch", "OpenSearchReportingSink"],
|
|
12
|
+
["Kafka", "KafkaReportingSink"],
|
|
13
|
+
["StatsD", "StatsDReportingSink"],
|
|
14
|
+
["DogStatsD", "DogStatsDReportingSink"],
|
|
15
|
+
["Netdata", "NetdataStatsDReportingSink"],
|
|
16
|
+
["Jsonl", "JsonlFileReportingSink"],
|
|
17
|
+
["Webhook", "GenericWebhookReportingSink"]
|
|
18
|
+
];
|
|
19
|
+
function assertNoUnsupportedReportingInfraConfig(infraConfig, sinks = []) {
|
|
20
|
+
if (!isRecord(infraConfig)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const selectedSections = selectedExpandedReportingSections(sinks);
|
|
24
|
+
for (const [sectionName, sinkType] of UNBOUND_EXPANDED_SECTIONS) {
|
|
25
|
+
if (selectedSections.has(sectionName)
|
|
26
|
+
&& hasNonEmptyReportingSectionAlias(infraConfig, sectionName)) {
|
|
27
|
+
throw new Error(`Configuration section 'LoadStrike:ReportingSinks:${sectionName}' is not bound automatically by the TypeScript SDK. `
|
|
28
|
+
+ `Construct ${sinkType} explicitly and pass its options directly.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function selectedExpandedReportingSections(sinks) {
|
|
33
|
+
const selected = new Set();
|
|
34
|
+
const visited = new Set();
|
|
35
|
+
const inspect = (sink) => {
|
|
36
|
+
if (!isRecord(sink) || visited.has(sink)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
visited.add(sink);
|
|
40
|
+
const identities = [
|
|
41
|
+
sink.licenseFeature,
|
|
42
|
+
sink.LicenseFeature,
|
|
43
|
+
sink.feature,
|
|
44
|
+
sink.Feature
|
|
45
|
+
];
|
|
46
|
+
if (!hasReportingSinkLifecycle(sink)) {
|
|
47
|
+
identities.push(sink.kind, sink.Kind);
|
|
48
|
+
}
|
|
49
|
+
const normalizedIdentities = identities
|
|
50
|
+
.map(normalizeExpandedSinkIdentity)
|
|
51
|
+
.filter(Boolean);
|
|
52
|
+
for (const identity of normalizedIdentities) {
|
|
53
|
+
const sectionName = EXPANDED_SECTION_BY_IDENTITY.get(identity);
|
|
54
|
+
if (sectionName) {
|
|
55
|
+
selected.add(sectionName);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const children = Array.isArray(sink.sinks)
|
|
59
|
+
? sink.sinks
|
|
60
|
+
: Array.isArray(sink.Sinks)
|
|
61
|
+
? sink.Sinks
|
|
62
|
+
: [];
|
|
63
|
+
for (const child of children) {
|
|
64
|
+
inspect(child);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
for (const sink of sinks) {
|
|
68
|
+
inspect(sink);
|
|
69
|
+
}
|
|
70
|
+
return selected;
|
|
71
|
+
}
|
|
72
|
+
const EXPANDED_SECTION_BY_IDENTITY = new Map([
|
|
73
|
+
["prometheusremotewrite", "PrometheusRemoteWrite"],
|
|
74
|
+
["cloudwatch", "CloudWatch"],
|
|
75
|
+
["dynatrace", "Dynatrace"],
|
|
76
|
+
["elasticsearch", "Elasticsearch"],
|
|
77
|
+
["opensearch", "OpenSearch"],
|
|
78
|
+
["newrelic", "NewRelic"],
|
|
79
|
+
["webhook", "Webhook"],
|
|
80
|
+
["genericwebhook", "Webhook"],
|
|
81
|
+
["kafka", "Kafka"],
|
|
82
|
+
["statsd", "StatsD"],
|
|
83
|
+
["dogstatsd", "DogStatsD"],
|
|
84
|
+
["netdata", "Netdata"],
|
|
85
|
+
["netdatastatsd", "Netdata"],
|
|
86
|
+
["jsonl", "Jsonl"],
|
|
87
|
+
["jsonlfile", "Jsonl"]
|
|
88
|
+
]);
|
|
89
|
+
function normalizeExpandedSinkIdentity(value) {
|
|
90
|
+
let normalized = normalizeIdentityToken(value);
|
|
91
|
+
const featurePrefix = "extensionsreportingsinks";
|
|
92
|
+
if (normalized.startsWith(featurePrefix)) {
|
|
93
|
+
normalized = normalized.slice(featurePrefix.length);
|
|
94
|
+
}
|
|
95
|
+
const classSuffix = "reportingsink";
|
|
96
|
+
if (normalized.endsWith(classSuffix)) {
|
|
97
|
+
normalized = normalized.slice(0, -classSuffix.length);
|
|
98
|
+
}
|
|
99
|
+
return normalized;
|
|
100
|
+
}
|
|
101
|
+
function assertNoUnsupportedReportingSinkGraph(sinks) {
|
|
102
|
+
const visited = new Set();
|
|
103
|
+
const active = new Set();
|
|
104
|
+
const inspect = (sink) => {
|
|
105
|
+
if (!isRecord(sink)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (active.has(sink)) {
|
|
109
|
+
throw new Error("Composite reporting sink graph cannot contain a cycle.");
|
|
110
|
+
}
|
|
111
|
+
if (visited.has(sink)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
active.add(sink);
|
|
115
|
+
const identityTokens = [sink.sinkName, sink.SinkName, sink.kind, sink.Kind]
|
|
116
|
+
.map(normalizeIdentityToken)
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
const featureTokens = [sink.licenseFeature, sink.LicenseFeature, sink.feature, sink.Feature]
|
|
119
|
+
.map((value) => String(value ?? "").trim().toLowerCase())
|
|
120
|
+
.filter(Boolean);
|
|
121
|
+
const hasLifecycle = hasReportingSinkLifecycle(sink);
|
|
122
|
+
const displayName = directVendorDisplayName(hasLifecycle ? [] : identityTokens, hasLifecycle ? [] : featureTokens);
|
|
123
|
+
if (displayName) {
|
|
124
|
+
throw unsupportedDirectVendorReportingError(displayName);
|
|
125
|
+
}
|
|
126
|
+
if (identityTokens.includes("composite") || identityTokens.includes("compositereportingsink")) {
|
|
127
|
+
const children = Array.isArray(sink.sinks)
|
|
128
|
+
? sink.sinks
|
|
129
|
+
: Array.isArray(sink.Sinks)
|
|
130
|
+
? sink.Sinks
|
|
131
|
+
: [];
|
|
132
|
+
for (const child of children) {
|
|
133
|
+
inspect(child);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
active.delete(sink);
|
|
137
|
+
visited.add(sink);
|
|
138
|
+
};
|
|
139
|
+
for (const sink of sinks) {
|
|
140
|
+
inspect(sink);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function hasNonEmptyReportingSectionAlias(infraConfig, sectionName) {
|
|
144
|
+
const segments = ["LoadStrike", "ReportingSinks", sectionName];
|
|
145
|
+
const values = [
|
|
146
|
+
...readDirectValuesCaseInsensitive(infraConfig, segments.join(":")),
|
|
147
|
+
...readDirectValuesCaseInsensitive(infraConfig, segments.join(".")),
|
|
148
|
+
...readNestedValuesCaseInsensitive(infraConfig, segments)
|
|
149
|
+
];
|
|
150
|
+
return values.some(isNonEmptySection);
|
|
151
|
+
}
|
|
152
|
+
function readDirectValuesCaseInsensitive(record, key) {
|
|
153
|
+
const expected = key.toLowerCase();
|
|
154
|
+
return Object.entries(record)
|
|
155
|
+
.filter(([candidate]) => candidate.toLowerCase() === expected)
|
|
156
|
+
.map(([, value]) => value);
|
|
157
|
+
}
|
|
158
|
+
function readNestedValuesCaseInsensitive(record, segments) {
|
|
159
|
+
let current = [record];
|
|
160
|
+
for (const segment of segments) {
|
|
161
|
+
const next = [];
|
|
162
|
+
for (const candidate of current) {
|
|
163
|
+
if (isRecord(candidate)) {
|
|
164
|
+
next.push(...readDirectValuesCaseInsensitive(candidate, segment));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (next.length === 0) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
current = next;
|
|
171
|
+
}
|
|
172
|
+
return current;
|
|
173
|
+
}
|
|
174
|
+
function isNonEmptySection(value) {
|
|
175
|
+
if (value == null) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (typeof value === "string") {
|
|
179
|
+
return value.trim().length > 0;
|
|
180
|
+
}
|
|
181
|
+
if (Array.isArray(value)) {
|
|
182
|
+
return value.length > 0;
|
|
183
|
+
}
|
|
184
|
+
if (isRecord(value)) {
|
|
185
|
+
return Object.keys(value).length > 0;
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
function directVendorDisplayName(identityTokens, featureTokens) {
|
|
190
|
+
if (identityTokens.includes("prometheusremotewrite")
|
|
191
|
+
|| identityTokens.includes("prometheusremotewritereportingsink")
|
|
192
|
+
|| featureTokens.includes("extensions.reporting_sinks.prometheus_remote_write")) {
|
|
193
|
+
return "Prometheus Remote Write";
|
|
194
|
+
}
|
|
195
|
+
if (identityTokens.includes("cloudwatch")
|
|
196
|
+
|| identityTokens.includes("cloudwatchreportingsink")
|
|
197
|
+
|| featureTokens.includes("extensions.reporting_sinks.cloudwatch")) {
|
|
198
|
+
return "CloudWatch";
|
|
199
|
+
}
|
|
200
|
+
if (identityTokens.includes("dynatrace")
|
|
201
|
+
|| identityTokens.includes("dynatracereportingsink")
|
|
202
|
+
|| featureTokens.includes("extensions.reporting_sinks.dynatrace")) {
|
|
203
|
+
return "Dynatrace";
|
|
204
|
+
}
|
|
205
|
+
if (identityTokens.includes("newrelic")
|
|
206
|
+
|| identityTokens.includes("newrelicreportingsink")
|
|
207
|
+
|| featureTokens.includes("extensions.reporting_sinks.new_relic")) {
|
|
208
|
+
return "New Relic";
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
function unsupportedDirectVendorReportingError(displayName) {
|
|
213
|
+
return new Error(`Direct ${displayName} reporting is not supported by the TypeScript SDK. `
|
|
214
|
+
+ "Use GenericWebhookReportingSink through a converting gateway or a custom LoadStrikeReportingSink.");
|
|
215
|
+
}
|
|
216
|
+
function normalizeIdentityToken(value) {
|
|
217
|
+
return String(value ?? "")
|
|
218
|
+
.trim()
|
|
219
|
+
.toLowerCase()
|
|
220
|
+
.replace(/[^a-z0-9]+/g, "");
|
|
221
|
+
}
|
|
222
|
+
function hasReportingSinkLifecycle(sink) {
|
|
223
|
+
return [
|
|
224
|
+
"init",
|
|
225
|
+
"Init",
|
|
226
|
+
"start",
|
|
227
|
+
"Start",
|
|
228
|
+
"saveRealtimeStats",
|
|
229
|
+
"SaveRealtimeStats",
|
|
230
|
+
"saveRealtimeMetrics",
|
|
231
|
+
"SaveRealtimeMetrics",
|
|
232
|
+
"saveRunResult",
|
|
233
|
+
"SaveRunResult",
|
|
234
|
+
"saveIterationBatch",
|
|
235
|
+
"SaveIterationBatch",
|
|
236
|
+
"stop",
|
|
237
|
+
"Stop"
|
|
238
|
+
].some((member) => typeof sink[member] === "function");
|
|
239
|
+
}
|
|
240
|
+
function isRecord(value) {
|
|
241
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
242
|
+
}
|
package/dist/cjs/runtime.js
CHANGED
|
@@ -16,12 +16,15 @@ const reporting_js_1 = require("./reporting.js");
|
|
|
16
16
|
const report_history_js_1 = require("./report-history.js");
|
|
17
17
|
const local_report_input_js_1 = require("./local-report-input.js");
|
|
18
18
|
const sinks_js_1 = require("./sinks.js");
|
|
19
|
+
const reporting_sink_http_error_js_1 = require("./internal/reporting-sink-http-error.js");
|
|
20
|
+
const reporting_containment_js_1 = require("./reporting-containment.js");
|
|
19
21
|
const load_engine_v2_js_1 = require("./load-engine-v2.js");
|
|
20
22
|
const iteration_observations_js_1 = require("./iteration-observations.js");
|
|
21
23
|
const iteration_observation_diagnostics_js_1 = require("./iteration-observation-diagnostics.js");
|
|
22
24
|
const sink_retry_policy_js_1 = require("./sink-retry-policy.js");
|
|
23
25
|
const LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS = Symbol("loadstrike.load-engine-v2.scheduler-distributions");
|
|
24
26
|
const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
|
|
27
|
+
const REPORTING_INTERVAL_SECONDS = Symbol.for("loadstrike.internal.reporting-interval-seconds");
|
|
25
28
|
exports.LoadStrikeNodeType = {
|
|
26
29
|
SingleNode: "SingleNode",
|
|
27
30
|
Coordinator: "Coordinator",
|
|
@@ -747,6 +750,15 @@ class ScenarioStatsAccumulator {
|
|
|
747
750
|
return attachScenarioStatsAliases(scenario);
|
|
748
751
|
}
|
|
749
752
|
}
|
|
753
|
+
function isRetryableReportingSinkError(sink, error) {
|
|
754
|
+
if (!(sink instanceof sinks_js_1.PrometheusRemoteWriteReportingSink)) {
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
if (!(error instanceof reporting_sink_http_error_js_1.ReportingSinkHttpError)) {
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
return error.status === 429 || error.status < 400 || error.status >= 500;
|
|
761
|
+
}
|
|
750
762
|
class LoadStrikeResponse {
|
|
751
763
|
/**
|
|
752
764
|
* Creates a successful reply.
|
|
@@ -3495,6 +3507,8 @@ class LoadStrikeRunner {
|
|
|
3495
3507
|
return this.buildContext();
|
|
3496
3508
|
}
|
|
3497
3509
|
async run(args = []) {
|
|
3510
|
+
(0, reporting_containment_js_1.assertNoUnsupportedReportingSinkGraph)(this.options.reportingSinks ?? []);
|
|
3511
|
+
assertNoUnsupportedNativeTrackingScenarios(this.scenarios);
|
|
3498
3512
|
if (this.contextConfigurators.length) {
|
|
3499
3513
|
return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions(), [], this.internalOptions).run(args);
|
|
3500
3514
|
}
|
|
@@ -3506,7 +3520,7 @@ class LoadStrikeRunner {
|
|
|
3506
3520
|
const scenarioAccumulators = new Map();
|
|
3507
3521
|
const scenarioDurationsMs = new Map();
|
|
3508
3522
|
const stepStats = new Map();
|
|
3509
|
-
const sinks = (this.options.reportingSinks ?? []).map((sink) => (0, sinks_js_1.cloneReportingSinkForRun)(sink));
|
|
3523
|
+
const sinks = (this.options.reportingSinks ?? []).map((sink) => (0, sinks_js_1.cloneReportingSinkForRun)(sink, this.options.infraConfig ?? {}));
|
|
3510
3524
|
const sinkStates = sinks.map((sink, index) => ({
|
|
3511
3525
|
sink,
|
|
3512
3526
|
disabled: false,
|
|
@@ -3631,6 +3645,12 @@ class LoadStrikeRunner {
|
|
|
3631
3645
|
testInfo,
|
|
3632
3646
|
getNodeInfo: () => attachNodeInfoAliases({ ...nodeInfo })
|
|
3633
3647
|
};
|
|
3648
|
+
Object.defineProperty(baseContext, REPORTING_INTERVAL_SECONDS, {
|
|
3649
|
+
value: this.options.reportingIntervalSeconds ?? 5,
|
|
3650
|
+
enumerable: false,
|
|
3651
|
+
configurable: false,
|
|
3652
|
+
writable: false
|
|
3653
|
+
});
|
|
3634
3654
|
attachBaseContextAliases(baseContext);
|
|
3635
3655
|
const sinkSession = {
|
|
3636
3656
|
startedUtc: createdUtc,
|
|
@@ -3685,6 +3705,7 @@ class LoadStrikeRunner {
|
|
|
3685
3705
|
name: state.name,
|
|
3686
3706
|
iterationObservationPortalSink: Boolean(state.sink.iterationObservationPortalSink),
|
|
3687
3707
|
iterationObservationShapeLimited: Boolean(state.sink.iterationObservationShapeLimited),
|
|
3708
|
+
retryableErrorClassifier: (error) => isRetryableReportingSinkError(state.sink, error),
|
|
3688
3709
|
saveIterationBatch: resolveSinkSaveIterationBatch(state.sink),
|
|
3689
3710
|
completeIterationObservationStream: resolveSinkCompleteIterationObservationStream(state.sink)
|
|
3690
3711
|
}));
|
|
@@ -4319,7 +4340,9 @@ class LoadStrikeRunner {
|
|
|
4319
4340
|
return;
|
|
4320
4341
|
}
|
|
4321
4342
|
catch (error) {
|
|
4322
|
-
const
|
|
4343
|
+
const retryable = isRetryableReportingSinkError(state.sink, error);
|
|
4344
|
+
const exhausted = attempts >= maximumAttempts || !retryable;
|
|
4345
|
+
const reportedMaximumAttempts = retryable ? maximumAttempts : attempts;
|
|
4323
4346
|
const nextDelayMs = exhausted ? 0 : (0, sink_retry_policy_js_1.sinkRetryDelayMs)(backoffMs, attempts);
|
|
4324
4347
|
(0, iteration_observation_diagnostics_js_1.logIterationObservationFailure)(logger, exhausted ? "error" : "warn", {
|
|
4325
4348
|
sinkName: state.name,
|
|
@@ -4329,7 +4352,7 @@ class LoadStrikeRunner {
|
|
|
4329
4352
|
resultOwnerId: "",
|
|
4330
4353
|
observationCount: 0,
|
|
4331
4354
|
attempt: attempts,
|
|
4332
|
-
maximumAttempts,
|
|
4355
|
+
maximumAttempts: reportedMaximumAttempts,
|
|
4333
4356
|
nextDelayMs
|
|
4334
4357
|
}, error);
|
|
4335
4358
|
if (exhausted) {
|
|
@@ -8896,6 +8919,7 @@ class ManagedScenarioTrackingRuntime {
|
|
|
8896
8919
|
}
|
|
8897
8920
|
async dispose() {
|
|
8898
8921
|
this.shutdown = true;
|
|
8922
|
+
this.interruptEndpointAdapters();
|
|
8899
8923
|
this.rejectOutstandingWaiters();
|
|
8900
8924
|
await Promise.all([
|
|
8901
8925
|
this.sourceLoop,
|
|
@@ -8961,6 +8985,7 @@ class ManagedScenarioTrackingRuntime {
|
|
|
8961
8985
|
failureSeen = failureSeen || this.isFailedObservationOutcome(outcome);
|
|
8962
8986
|
}
|
|
8963
8987
|
this.shutdown = true;
|
|
8988
|
+
this.interruptEndpointAdapters();
|
|
8964
8989
|
await Promise.all([
|
|
8965
8990
|
this.sourceLoop,
|
|
8966
8991
|
this.destinationLoop,
|
|
@@ -8977,6 +9002,20 @@ class ManagedScenarioTrackingRuntime {
|
|
|
8977
9002
|
? LoadStrikeResponse.fail("tracking_failures", "One or more observed source or destination events did not correlate successfully.", 0)
|
|
8978
9003
|
: LoadStrikeResponse.ok("observed");
|
|
8979
9004
|
}
|
|
9005
|
+
interruptEndpointAdapters() {
|
|
9006
|
+
try {
|
|
9007
|
+
this.sourceAdapter.interrupt?.();
|
|
9008
|
+
}
|
|
9009
|
+
catch {
|
|
9010
|
+
// Shutdown remains best-effort and still disposes every adapter below.
|
|
9011
|
+
}
|
|
9012
|
+
try {
|
|
9013
|
+
this.destinationAdapter?.interrupt?.();
|
|
9014
|
+
}
|
|
9015
|
+
catch {
|
|
9016
|
+
// Shutdown remains best-effort and still disposes every adapter below.
|
|
9017
|
+
}
|
|
9018
|
+
}
|
|
8980
9019
|
async consumeSourceLoop() {
|
|
8981
9020
|
while (!this.shutdown) {
|
|
8982
9021
|
try {
|
|
@@ -9568,6 +9607,20 @@ function mapRuntimeTrackingEndpointSpec(spec, useLoadStrikeTraceIdHeader = false
|
|
|
9568
9607
|
azureEventHubs: asTrackingRecord(pickTrackingValue(spec, "AzureEventHubs", "azureEventHubs")),
|
|
9569
9608
|
sqs: asTrackingRecord(pickTrackingValue(spec, "Sqs", "sqs")),
|
|
9570
9609
|
pushDiffusion: asTrackingRecord(pickTrackingValue(spec, "PushDiffusion", "pushDiffusion")),
|
|
9610
|
+
grpc: mapRuntimeEndpointProtocolOptions(spec, ["Grpc", "grpc"], [
|
|
9611
|
+
"Target", "target", "ServiceName", "serviceName", "MethodName", "methodName",
|
|
9612
|
+
"MethodType", "methodType", "Deadline", "deadline", "DeadlineSeconds", "deadlineSeconds",
|
|
9613
|
+
"DeadlineMs", "deadlineMs", "Metadata", "metadata", "Produce", "produce", "Consume", "consume",
|
|
9614
|
+
"ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync", "ConnectionMetadata", "connectionMetadata",
|
|
9615
|
+
"NativeClient", "nativeClient"
|
|
9616
|
+
]),
|
|
9617
|
+
webSocket: mapRuntimeEndpointProtocolOptions(spec, ["WebSocket", "webSocket"], [
|
|
9618
|
+
"Url", "url", "Subprotocols", "subprotocols", "ConnectTimeout", "connectTimeout",
|
|
9619
|
+
"ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeoutMs", "connectTimeoutMs",
|
|
9620
|
+
"CloseTimeout", "closeTimeout", "CloseTimeoutSeconds", "closeTimeoutSeconds", "CloseTimeoutMs", "closeTimeoutMs",
|
|
9621
|
+
"Produce", "produce", "Consume", "consume", "ProduceAsync", "produceAsync", "ConsumeAsync", "consumeAsync",
|
|
9622
|
+
"ConnectionMetadata", "connectionMetadata", "NativeClient", "nativeClient"
|
|
9623
|
+
]),
|
|
9571
9624
|
delegate: typeof delegateProduce === "function"
|
|
9572
9625
|
|| typeof delegateConsume === "function"
|
|
9573
9626
|
|| typeof delegateProduceAsync === "function"
|
|
@@ -9590,6 +9643,19 @@ function mapRuntimeTrackingEndpointSpec(spec, useLoadStrikeTraceIdHeader = false
|
|
|
9590
9643
|
: undefined
|
|
9591
9644
|
};
|
|
9592
9645
|
}
|
|
9646
|
+
function mapRuntimeEndpointProtocolOptions(spec, nestedKeys, flatKeys) {
|
|
9647
|
+
const nested = asTrackingRecord(pickTrackingValue(spec, ...nestedKeys));
|
|
9648
|
+
if (Object.keys(nested).length > 0) {
|
|
9649
|
+
return nested;
|
|
9650
|
+
}
|
|
9651
|
+
const options = {};
|
|
9652
|
+
for (const key of flatKeys) {
|
|
9653
|
+
if (Object.prototype.hasOwnProperty.call(spec, key)) {
|
|
9654
|
+
options[key] = pickTrackingValue(spec, key);
|
|
9655
|
+
}
|
|
9656
|
+
}
|
|
9657
|
+
return options;
|
|
9658
|
+
}
|
|
9593
9659
|
function normalizeRuntimeTrackingPayload(payload, endpoint, index) {
|
|
9594
9660
|
const normalized = {
|
|
9595
9661
|
headers: {
|
|
@@ -10353,6 +10419,20 @@ function assertNoDisableLicenseEnforcementOption(value, source) {
|
|
|
10353
10419
|
}
|
|
10354
10420
|
}
|
|
10355
10421
|
}
|
|
10422
|
+
function assertNoUnsupportedNativeTrackingScenarios(scenarios) {
|
|
10423
|
+
for (const scenario of scenarios) {
|
|
10424
|
+
const tracking = scenario.getTrackingConfiguration();
|
|
10425
|
+
if (!tracking) {
|
|
10426
|
+
continue;
|
|
10427
|
+
}
|
|
10428
|
+
for (const field of ["Source", "Destination"]) {
|
|
10429
|
+
const endpoint = asTrackingRecord(pickTrackingValue(tracking, field, field.toLowerCase()));
|
|
10430
|
+
if (Object.keys(endpoint).length > 0) {
|
|
10431
|
+
(0, transports_js_1.validateNativeEndpointExecutionSupport)(endpoint);
|
|
10432
|
+
}
|
|
10433
|
+
}
|
|
10434
|
+
}
|
|
10435
|
+
}
|
|
10356
10436
|
function normalizeRunContextCollectionShapes(values) {
|
|
10357
10437
|
assertNoDisableLicenseEnforcementOption(values, "LoadStrikeContext");
|
|
10358
10438
|
const normalized = {
|