@loadstrike/loadstrike-sdk 1.0.30401 → 1.0.31001
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 +4 -0
- package/dist/cjs/iteration-observation-diagnostics.js +513 -0
- package/dist/cjs/iteration-observations.js +195 -22
- package/dist/cjs/local.js +55 -10
- package/dist/cjs/reporting.js +26 -7
- package/dist/cjs/runtime.js +159 -80
- package/dist/cjs/sink-retry-policy.js +52 -0
- package/dist/cjs/sinks.js +112 -9
- package/dist/cjs/transports.js +78 -25
- package/dist/esm/iteration-observation-diagnostics.js +508 -0
- package/dist/esm/iteration-observations.js +195 -22
- package/dist/esm/local.js +55 -10
- package/dist/esm/reporting.js +26 -7
- package/dist/esm/runtime.js +159 -80
- package/dist/esm/sink-retry-policy.js +44 -0
- package/dist/esm/sinks.js +112 -9
- package/dist/esm/transports.js +78 -25
- package/dist/types/iteration-observation-diagnostics.d.ts +21 -0
- package/dist/types/iteration-observations.d.ts +5 -0
- package/dist/types/runtime.d.ts +2 -0
- package/dist/types/sink-retry-policy.d.ts +9 -0
- package/dist/types/sinks.d.ts +6 -0
- package/dist/types/transports.d.ts +4 -0
- package/package.json +2 -2
package/dist/esm/sinks.js
CHANGED
|
@@ -6,7 +6,26 @@ import { createSocket } from "node:dgram";
|
|
|
6
6
|
import { gzip } from "node:zlib";
|
|
7
7
|
import { Pool } from "pg";
|
|
8
8
|
import { serializeIterationObservationBatchGzipJson } from "./iteration-observations.js";
|
|
9
|
+
import { redactIterationObservationSecrets } from "./iteration-observation-diagnostics.js";
|
|
9
10
|
const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
|
|
11
|
+
const MAXIMUM_HTTP_RESPONSE_BODY_BYTES = 256 * 1024;
|
|
12
|
+
const MAXIMUM_HTTP_METADATA_CHARACTERS = 256;
|
|
13
|
+
const HTTP_METADATA_TRUNCATION_SUFFIX = " [truncated]";
|
|
14
|
+
class ReportingSinkHttpError extends Error {
|
|
15
|
+
constructor(sinkName, status, statusText, requestId) {
|
|
16
|
+
const normalizedStatus = Number.isFinite(status) ? Math.max(Math.trunc(status), 0) : 0;
|
|
17
|
+
const normalizedStatusText = normalizeHttpMetadata(statusText);
|
|
18
|
+
const normalizedRequestId = normalizeHttpMetadata(requestId);
|
|
19
|
+
super(`${sinkName} write failed with HTTP status ${normalizedStatus}`
|
|
20
|
+
+ (normalizedStatusText ? ` ${normalizedStatusText}` : "")
|
|
21
|
+
+ (normalizedRequestId ? ` (requestId=${normalizedRequestId})` : "")
|
|
22
|
+
+ ".");
|
|
23
|
+
this.name = "ReportingSinkHttpError";
|
|
24
|
+
this.status = normalizedStatus;
|
|
25
|
+
this.statusText = normalizedStatusText;
|
|
26
|
+
this.requestId = normalizedRequestId;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
10
29
|
function gzipJsonAsync(value) {
|
|
11
30
|
const json = Buffer.from(JSON.stringify(value), "utf8");
|
|
12
31
|
return new Promise((resolve, reject) => {
|
|
@@ -2202,6 +2221,7 @@ function removeSdkPercentileFields(event) {
|
|
|
2202
2221
|
}
|
|
2203
2222
|
function createIterationObservationEvents(session, batch) {
|
|
2204
2223
|
const events = [];
|
|
2224
|
+
const occurredUtc = dateFromUtcNanoseconds(batch.createdUtcNs);
|
|
2205
2225
|
for (const observation of batch.observations) {
|
|
2206
2226
|
const tags = {
|
|
2207
2227
|
run_id: batch.runId,
|
|
@@ -2214,7 +2234,7 @@ function createIterationObservationEvents(session, batch) {
|
|
|
2214
2234
|
events.push({
|
|
2215
2235
|
runId: batch.runId,
|
|
2216
2236
|
eventType: "iteration.observation",
|
|
2217
|
-
occurredUtc
|
|
2237
|
+
occurredUtc,
|
|
2218
2238
|
sessionId: batch.sessionId,
|
|
2219
2239
|
testSuite: session.testSuite,
|
|
2220
2240
|
testName: session.testName,
|
|
@@ -2249,7 +2269,7 @@ function createIterationObservationEvents(session, batch) {
|
|
|
2249
2269
|
events.push({
|
|
2250
2270
|
runId: batch.runId,
|
|
2251
2271
|
eventType: "iteration.step",
|
|
2252
|
-
occurredUtc
|
|
2272
|
+
occurredUtc,
|
|
2253
2273
|
sessionId: batch.sessionId,
|
|
2254
2274
|
testSuite: session.testSuite,
|
|
2255
2275
|
testName: session.testName,
|
|
@@ -2280,10 +2300,11 @@ function createIterationObservationEvents(session, batch) {
|
|
|
2280
2300
|
return events;
|
|
2281
2301
|
}
|
|
2282
2302
|
function createIterationCompletionEvents(session, completion) {
|
|
2303
|
+
const occurredUtc = dateFromUtcNanoseconds(completion.completedUtcNs);
|
|
2283
2304
|
return [{
|
|
2284
2305
|
runId: completion.runId,
|
|
2285
2306
|
eventType: "observation.stream.completed",
|
|
2286
|
-
occurredUtc
|
|
2307
|
+
occurredUtc,
|
|
2287
2308
|
sessionId: completion.sessionId,
|
|
2288
2309
|
testSuite: session.testSuite,
|
|
2289
2310
|
testName: session.testName,
|
|
@@ -2342,6 +2363,22 @@ function portalEventId(event, index) {
|
|
|
2342
2363
|
});
|
|
2343
2364
|
return `lsr_${createHash("sha256").update(material).digest("hex").slice(0, 40)}`;
|
|
2344
2365
|
}
|
|
2366
|
+
function dateFromUtcNanoseconds(value) {
|
|
2367
|
+
try {
|
|
2368
|
+
const nanoseconds = BigInt(value);
|
|
2369
|
+
if (nanoseconds < 0n) {
|
|
2370
|
+
return new Date(0);
|
|
2371
|
+
}
|
|
2372
|
+
const milliseconds = nanoseconds / 1000000n;
|
|
2373
|
+
const numeric = Number(milliseconds);
|
|
2374
|
+
return Number.isFinite(numeric) && numeric <= 8640000000000000
|
|
2375
|
+
? new Date(numeric)
|
|
2376
|
+
: new Date(0);
|
|
2377
|
+
}
|
|
2378
|
+
catch {
|
|
2379
|
+
return new Date(0);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2345
2382
|
function addMeasurementFields(fields, prefix, measurement) {
|
|
2346
2383
|
const request = measurement?.request ?? { count: 0, percent: 0, rps: 0 };
|
|
2347
2384
|
const latency = measurement?.latency ?? {
|
|
@@ -3533,22 +3570,85 @@ async function postWithTimeout(fetchImpl, url, init, timeoutMs, sinkName) {
|
|
|
3533
3570
|
const timer = setTimeout(() => controller.abort(), Math.max(timeoutMs, 1));
|
|
3534
3571
|
try {
|
|
3535
3572
|
const response = await fetchImpl(url, { ...init, signal: controller.signal });
|
|
3536
|
-
const body = await readResponseBodyText(response);
|
|
3537
3573
|
if (!response.ok) {
|
|
3538
|
-
throw new
|
|
3574
|
+
throw new ReportingSinkHttpError(sinkName, response.status, readHttpResponseStatusText(response), readHttpResponseRequestId(response));
|
|
3539
3575
|
}
|
|
3540
|
-
return
|
|
3576
|
+
return await readResponseBodyText(response);
|
|
3541
3577
|
}
|
|
3542
3578
|
finally {
|
|
3543
3579
|
clearTimeout(timer);
|
|
3544
3580
|
}
|
|
3545
3581
|
}
|
|
3546
3582
|
async function readResponseBodyText(response) {
|
|
3547
|
-
const
|
|
3548
|
-
if (typeof
|
|
3583
|
+
const body = response.body;
|
|
3584
|
+
if (body && typeof body.getReader === "function") {
|
|
3585
|
+
const reader = body.getReader();
|
|
3586
|
+
const chunks = [];
|
|
3587
|
+
let bytes = 0;
|
|
3588
|
+
try {
|
|
3589
|
+
while (true) {
|
|
3590
|
+
const next = await reader.read();
|
|
3591
|
+
if (next.done)
|
|
3592
|
+
break;
|
|
3593
|
+
if (!next.value)
|
|
3594
|
+
continue;
|
|
3595
|
+
if (bytes + next.value.byteLength > MAXIMUM_HTTP_RESPONSE_BODY_BYTES) {
|
|
3596
|
+
await reader.cancel();
|
|
3597
|
+
throw new Error("Reporting sink response exceeded the bounded response limit.");
|
|
3598
|
+
}
|
|
3599
|
+
chunks.push(next.value);
|
|
3600
|
+
bytes += next.value.byteLength;
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
finally {
|
|
3604
|
+
reader.releaseLock();
|
|
3605
|
+
}
|
|
3606
|
+
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString("utf8");
|
|
3607
|
+
}
|
|
3608
|
+
return "";
|
|
3609
|
+
}
|
|
3610
|
+
function readHttpResponseStatusText(response) {
|
|
3611
|
+
try {
|
|
3612
|
+
return normalizeHttpMetadata(response.statusText);
|
|
3613
|
+
}
|
|
3614
|
+
catch {
|
|
3615
|
+
return "";
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
function readHttpResponseRequestId(response) {
|
|
3619
|
+
for (const name of [
|
|
3620
|
+
"x-request-id",
|
|
3621
|
+
"x-correlation-id",
|
|
3622
|
+
"request-id",
|
|
3623
|
+
"traceparent"
|
|
3624
|
+
]) {
|
|
3625
|
+
try {
|
|
3626
|
+
const value = response.headers?.get(name);
|
|
3627
|
+
if (value) {
|
|
3628
|
+
return normalizeHttpMetadata(value);
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
catch {
|
|
3632
|
+
return "";
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
return "";
|
|
3636
|
+
}
|
|
3637
|
+
function normalizeHttpMetadata(value) {
|
|
3638
|
+
let text;
|
|
3639
|
+
try {
|
|
3640
|
+
const source = String(value ?? "");
|
|
3641
|
+
text = source.length <= MAXIMUM_HTTP_METADATA_CHARACTERS
|
|
3642
|
+
? source
|
|
3643
|
+
: source.slice(0, MAXIMUM_HTTP_METADATA_CHARACTERS - HTTP_METADATA_TRUNCATION_SUFFIX.length) + HTTP_METADATA_TRUNCATION_SUFFIX;
|
|
3644
|
+
}
|
|
3645
|
+
catch {
|
|
3549
3646
|
return "";
|
|
3550
3647
|
}
|
|
3551
|
-
return
|
|
3648
|
+
return redactIterationObservationSecrets(Buffer.from(text, "utf8").toString("utf8")
|
|
3649
|
+
.replace(/[\u0000-\u001F\u007F-\u009F]/gu, " ")
|
|
3650
|
+
.replace(/\s{2,}/gu, " ")
|
|
3651
|
+
.trim());
|
|
3552
3652
|
}
|
|
3553
3653
|
function validatePortalIngestResponse(responseBody) {
|
|
3554
3654
|
const text = String(responseBody ?? "").trim();
|
|
@@ -3601,6 +3701,8 @@ export const __loadstrikeTestExports = {
|
|
|
3601
3701
|
cloneScenarioStats,
|
|
3602
3702
|
cloneSessionStartInfo,
|
|
3603
3703
|
createFinalStatsEvents,
|
|
3704
|
+
createIterationCompletionEvents,
|
|
3705
|
+
createIterationObservationEvents,
|
|
3604
3706
|
createRealtimeStatsEvents,
|
|
3605
3707
|
createRunResultEvents,
|
|
3606
3708
|
createReportingEvent,
|
|
@@ -3617,6 +3719,7 @@ export const __loadstrikeTestExports = {
|
|
|
3617
3719
|
normalizeStringMap,
|
|
3618
3720
|
optionNumber,
|
|
3619
3721
|
optionString,
|
|
3722
|
+
portalEventPayload,
|
|
3620
3723
|
pickBooleanValue,
|
|
3621
3724
|
pickRecordValue,
|
|
3622
3725
|
postWithTimeout,
|
package/dist/esm/transports.js
CHANGED
|
@@ -596,14 +596,10 @@ class WebSocketEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
|
|
|
596
596
|
parsed = new URL(url);
|
|
597
597
|
}
|
|
598
598
|
catch {
|
|
599
|
-
|
|
600
|
-
// nosemgrep: vulnerability-tools.semgrep-rules.javascript.lang.security.detect-insecure-websocket
|
|
601
|
-
throw new Error("Url must be an absolute ws:// or wss:// URI.");
|
|
599
|
+
throw new Error("Url must be an absolute WebSocket URI using the ws or wss scheme.");
|
|
602
600
|
}
|
|
603
601
|
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
|
|
604
|
-
|
|
605
|
-
// nosemgrep: vulnerability-tools.semgrep-rules.javascript.lang.security.detect-insecure-websocket
|
|
606
|
-
throw new Error("Url must be an absolute ws:// or wss:// URI.");
|
|
602
|
+
throw new Error("Url must be an absolute WebSocket URI using the ws or wss scheme.");
|
|
607
603
|
}
|
|
608
604
|
if (this.ConnectTimeoutSeconds <= 0) {
|
|
609
605
|
throw new RangeError("ConnectTimeout must be greater than zero.");
|
|
@@ -3132,14 +3128,10 @@ function validateWebSocketEndpoint(endpoint, mode) {
|
|
|
3132
3128
|
parsed = new URL(url);
|
|
3133
3129
|
}
|
|
3134
3130
|
catch {
|
|
3135
|
-
|
|
3136
|
-
// nosemgrep: vulnerability-tools.semgrep-rules.javascript.lang.security.detect-insecure-websocket
|
|
3137
|
-
throw new Error("Url must be an absolute ws:// or wss:// URI.");
|
|
3131
|
+
throw new Error("Url must be an absolute WebSocket URI using the ws or wss scheme.");
|
|
3138
3132
|
}
|
|
3139
3133
|
if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
|
|
3140
|
-
|
|
3141
|
-
// nosemgrep: vulnerability-tools.semgrep-rules.javascript.lang.security.detect-insecure-websocket
|
|
3142
|
-
throw new Error("Url must be an absolute ws:// or wss:// URI.");
|
|
3134
|
+
throw new Error("Url must be an absolute WebSocket URI using the ws or wss scheme.");
|
|
3143
3135
|
}
|
|
3144
3136
|
const connectMs = optionNumber(options, "ConnectTimeoutMs", "connectTimeoutMs");
|
|
3145
3137
|
const connectSeconds = optionNumber(options, "ConnectTimeoutSeconds", "connectTimeoutSeconds", "ConnectTimeout", "connectTimeout");
|
|
@@ -3569,7 +3561,13 @@ function extractJsonPath(value, arrayPath) {
|
|
|
3569
3561
|
if (!normalized) {
|
|
3570
3562
|
return value;
|
|
3571
3563
|
}
|
|
3572
|
-
|
|
3564
|
+
let tokens;
|
|
3565
|
+
try {
|
|
3566
|
+
tokens = safeJsonPathSegments(normalized.replace(/\[(\d+)\]/g, ".$1"));
|
|
3567
|
+
}
|
|
3568
|
+
catch {
|
|
3569
|
+
return null;
|
|
3570
|
+
}
|
|
3573
3571
|
for (const token of tokens) {
|
|
3574
3572
|
if (current == null) {
|
|
3575
3573
|
return null;
|
|
@@ -3579,13 +3577,17 @@ function extractJsonPath(value, arrayPath) {
|
|
|
3579
3577
|
if (!Number.isInteger(index) || index < 0 || index >= current.length) {
|
|
3580
3578
|
return null;
|
|
3581
3579
|
}
|
|
3582
|
-
current = current
|
|
3580
|
+
current = current.at(index);
|
|
3583
3581
|
continue;
|
|
3584
3582
|
}
|
|
3585
|
-
if (!isRecord(current)
|
|
3583
|
+
if (!isRecord(current)) {
|
|
3586
3584
|
return null;
|
|
3587
3585
|
}
|
|
3588
|
-
|
|
3586
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, token);
|
|
3587
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
3588
|
+
return null;
|
|
3589
|
+
}
|
|
3590
|
+
current = descriptor.value;
|
|
3589
3591
|
}
|
|
3590
3592
|
return current;
|
|
3591
3593
|
}
|
|
@@ -3602,7 +3604,7 @@ function parseMaybeJson(value) {
|
|
|
3602
3604
|
}
|
|
3603
3605
|
}
|
|
3604
3606
|
function headersToRecord(value) {
|
|
3605
|
-
const headers =
|
|
3607
|
+
const headers = Object.create(null);
|
|
3606
3608
|
value.forEach((headerValue, key) => {
|
|
3607
3609
|
headers[key] = headerValue;
|
|
3608
3610
|
});
|
|
@@ -3956,11 +3958,22 @@ function extractTrackingValue(payload, selector) {
|
|
|
3956
3958
|
return null;
|
|
3957
3959
|
}
|
|
3958
3960
|
let current = body;
|
|
3959
|
-
|
|
3961
|
+
const path = selector.slice("json:".length).trim().replace(/^\$\./, "");
|
|
3962
|
+
let segments;
|
|
3963
|
+
try {
|
|
3964
|
+
segments = safeJsonPathSegments(path);
|
|
3965
|
+
}
|
|
3966
|
+
catch {
|
|
3967
|
+
return null;
|
|
3968
|
+
}
|
|
3969
|
+
for (const segment of segments) {
|
|
3960
3970
|
if (!isRecord(current)) {
|
|
3961
3971
|
return null;
|
|
3962
3972
|
}
|
|
3963
|
-
current
|
|
3973
|
+
if (!Object.prototype.hasOwnProperty.call(current, segment)) {
|
|
3974
|
+
return null;
|
|
3975
|
+
}
|
|
3976
|
+
current = readOwnJsonProperty(current, segment);
|
|
3964
3977
|
}
|
|
3965
3978
|
return current == null ? null : String(current);
|
|
3966
3979
|
}
|
|
@@ -3991,23 +4004,61 @@ function injectTrackingValue(payload, selector, value) {
|
|
|
3991
4004
|
}
|
|
3992
4005
|
function setJsonBodyValue(body, path, value) {
|
|
3993
4006
|
const target = parseBodyObject(body) ?? {};
|
|
3994
|
-
const clone =
|
|
3995
|
-
const segments = path
|
|
4007
|
+
const clone = cloneJsonRecord(target);
|
|
4008
|
+
const segments = safeJsonPathSegments(path);
|
|
3996
4009
|
if (!segments.length) {
|
|
3997
4010
|
return clone;
|
|
3998
4011
|
}
|
|
3999
4012
|
let current = clone;
|
|
4000
4013
|
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
4001
4014
|
const segment = segments[i];
|
|
4002
|
-
|
|
4015
|
+
let next = readOwnJsonProperty(current, segment);
|
|
4003
4016
|
if (!isRecord(next)) {
|
|
4004
|
-
|
|
4017
|
+
next = {};
|
|
4018
|
+
defineJsonProperty(current, segment, next);
|
|
4005
4019
|
}
|
|
4006
|
-
current =
|
|
4020
|
+
current = next;
|
|
4007
4021
|
}
|
|
4008
|
-
current
|
|
4022
|
+
defineJsonProperty(current, segments[segments.length - 1], value);
|
|
4009
4023
|
return clone;
|
|
4010
4024
|
}
|
|
4025
|
+
const FORBIDDEN_JSON_PATH_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
4026
|
+
function safeJsonPathSegments(path) {
|
|
4027
|
+
const segments = path.split(".").filter(Boolean);
|
|
4028
|
+
const forbidden = segments.find((segment) => FORBIDDEN_JSON_PATH_SEGMENTS.has(segment));
|
|
4029
|
+
if (forbidden) {
|
|
4030
|
+
throw new Error(`Tracking selector contains forbidden JSON path segment '${forbidden}'.`);
|
|
4031
|
+
}
|
|
4032
|
+
return segments;
|
|
4033
|
+
}
|
|
4034
|
+
function defineJsonProperty(target, key, value) {
|
|
4035
|
+
Object.defineProperty(target, key, {
|
|
4036
|
+
configurable: true,
|
|
4037
|
+
enumerable: true,
|
|
4038
|
+
value,
|
|
4039
|
+
writable: true
|
|
4040
|
+
});
|
|
4041
|
+
}
|
|
4042
|
+
function readOwnJsonProperty(target, key) {
|
|
4043
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
4044
|
+
return descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
4045
|
+
}
|
|
4046
|
+
function cloneJsonRecord(source) {
|
|
4047
|
+
const target = {};
|
|
4048
|
+
for (const [key, value] of Object.entries(source)) {
|
|
4049
|
+
defineJsonProperty(target, key, cloneJsonValue(value));
|
|
4050
|
+
}
|
|
4051
|
+
return target;
|
|
4052
|
+
}
|
|
4053
|
+
function cloneJsonValue(value) {
|
|
4054
|
+
if (Array.isArray(value)) {
|
|
4055
|
+
return value.map((entry) => cloneJsonValue(entry));
|
|
4056
|
+
}
|
|
4057
|
+
if (isRecord(value)) {
|
|
4058
|
+
return cloneJsonRecord(value);
|
|
4059
|
+
}
|
|
4060
|
+
return value;
|
|
4061
|
+
}
|
|
4011
4062
|
function parseBodyObject(body) {
|
|
4012
4063
|
if (body instanceof Uint8Array) {
|
|
4013
4064
|
return parseBodyObject(new TextDecoder().decode(body));
|
|
@@ -4616,8 +4667,10 @@ export const __loadstrikeTestExports = {
|
|
|
4616
4667
|
createNatsHeaders,
|
|
4617
4668
|
createRedisStreamPayload,
|
|
4618
4669
|
deserializeBrokerPayloadBody,
|
|
4670
|
+
extractJsonPath,
|
|
4619
4671
|
extractTrackingValue,
|
|
4620
4672
|
fromKafkaHeaders,
|
|
4673
|
+
headersToRecord,
|
|
4621
4674
|
headerValue,
|
|
4622
4675
|
attachDotNetTrackingPayloadAliases,
|
|
4623
4676
|
attachPayloadHelpers,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface IterationObservationDiagnosticLogger {
|
|
2
|
+
debug(message: string): void | PromiseLike<void>;
|
|
3
|
+
warn(message: string): void | PromiseLike<void>;
|
|
4
|
+
error(message: string): void | PromiseLike<void>;
|
|
5
|
+
}
|
|
6
|
+
export interface IterationObservationDiagnosticContext {
|
|
7
|
+
sinkName: string;
|
|
8
|
+
operation: "observation-batch" | "observation-stream-completion" | "reporting-sink-action";
|
|
9
|
+
phase?: string;
|
|
10
|
+
runId: string;
|
|
11
|
+
resultOwnerId: string;
|
|
12
|
+
batchId?: string;
|
|
13
|
+
batchSequence64?: string;
|
|
14
|
+
observationCount: number;
|
|
15
|
+
attempt: number;
|
|
16
|
+
maximumAttempts: number;
|
|
17
|
+
nextDelayMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function logIterationObservationFailure(logger: IterationObservationDiagnosticLogger | undefined, level: "warn" | "error", context: IterationObservationDiagnosticContext, error: unknown): void;
|
|
20
|
+
export declare function logIterationObservationRecovery(logger: IterationObservationDiagnosticLogger | undefined, context: IterationObservationDiagnosticContext): void;
|
|
21
|
+
export declare function redactIterationObservationSecrets(value: string): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type IterationObservationDiagnosticLogger } from "./iteration-observation-diagnostics.js";
|
|
1
2
|
export declare const ITERATION_OBSERVATION_SCHEMA_VERSION = "loadstrike.iteration-observation/1";
|
|
2
3
|
export declare const ITERATION_OBSERVATION_BATCH_SCHEMA_VERSION = "loadstrike.iteration-batch/1";
|
|
3
4
|
export declare const ITERATION_OBSERVATION_STREAM_COMPLETION_SCHEMA_VERSION = "loadstrike.iteration-stream-completion/1";
|
|
@@ -192,6 +193,9 @@ export interface IterationObservationReporterOptions {
|
|
|
192
193
|
buffer?: ProcessIterationObservationBuffer;
|
|
193
194
|
clock?: IterationObservationClock;
|
|
194
195
|
skipValidation?: boolean;
|
|
196
|
+
sinkRetryCount?: number;
|
|
197
|
+
sinkRetryBackoffMs?: number;
|
|
198
|
+
logger?: IterationObservationDiagnosticLogger;
|
|
195
199
|
}
|
|
196
200
|
export declare class IterationObservationReporter {
|
|
197
201
|
private readonly options;
|
|
@@ -215,6 +219,7 @@ export declare class IterationObservationReporter {
|
|
|
215
219
|
capture(observation: LoadStrikeIterationObservationV1): boolean;
|
|
216
220
|
flushNow(): void;
|
|
217
221
|
sealAndDrain(): Promise<IterationObservationDeliveryResult>;
|
|
222
|
+
private completeWithRetries;
|
|
218
223
|
buildWarnings(): IterationObservationGeneratorWarning[];
|
|
219
224
|
private createBatch;
|
|
220
225
|
private createCompletion;
|
package/dist/types/runtime.d.ts
CHANGED
|
@@ -2526,6 +2526,7 @@ declare function normalizeRuntimeTrackingPayload(payload: TrackingPayload | null
|
|
|
2526
2526
|
declare function readRuntimeTrackingId(payload: TrackingPayload, selector: string): string | null;
|
|
2527
2527
|
declare function runtimeParseBodyAsObject(body: unknown): Record<string, unknown> | null;
|
|
2528
2528
|
declare function setRuntimeJsonPathValue(body: unknown, path: string, value: string): unknown;
|
|
2529
|
+
declare function pickTrackingValue(source: Record<string, unknown>, ...keys: string[]): unknown;
|
|
2529
2530
|
declare function pickOptionalTrackingSelectorString(source: Record<string, unknown>, key: string, altKey: string): string | undefined;
|
|
2530
2531
|
declare function pickTrackingNumber(source: Record<string, unknown>, keys: string[], fallback: number): number;
|
|
2531
2532
|
declare function toTrackingStringMap(value: unknown): Record<string, string>;
|
|
@@ -2691,6 +2692,7 @@ export declare const __loadstrikeTestExports: {
|
|
|
2691
2692
|
parseStrictBooleanToken: typeof parseStrictBooleanToken;
|
|
2692
2693
|
percentile: typeof percentile;
|
|
2693
2694
|
pickOptionalTrackingSelectorString: typeof pickOptionalTrackingSelectorString;
|
|
2695
|
+
pickTrackingValue: typeof pickTrackingValue;
|
|
2694
2696
|
pickTrackingNumber: typeof pickTrackingNumber;
|
|
2695
2697
|
produceOrConsumeTrackingPayload: typeof produceOrConsumeTrackingPayload;
|
|
2696
2698
|
readConfiguredSinkName: typeof readConfiguredSinkName;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const DEFAULT_SINK_RETRY_COUNT = 3;
|
|
2
|
+
export declare const DEFAULT_SINK_RETRY_BACKOFF_MS = 250;
|
|
3
|
+
export declare const MAXIMUM_SINK_RETRY_COUNT = 100;
|
|
4
|
+
export declare const MAXIMUM_TIMER_DELAY_MS = 2147483647;
|
|
5
|
+
export declare function normalizeSinkRetryCount(value: number | undefined): number;
|
|
6
|
+
export declare function normalizeSinkRetryBackoffMs(value: number | undefined): number;
|
|
7
|
+
export declare function sinkRetryDelayMs(baseDelayMs: number, retryNumber: number): number;
|
|
8
|
+
export declare function waitForSinkRetryDelay(delayMs: number): Promise<void>;
|
|
9
|
+
export declare function cooperativeSinkRetryYield(): Promise<void>;
|
package/dist/types/sinks.d.ts
CHANGED
|
@@ -849,6 +849,9 @@ declare function createRealtimeStatsEvents(session: SinkSessionMetadata, scenari
|
|
|
849
849
|
declare function createFinalStatsEvents(session: SinkSessionMetadata, stats: LoadStrikeNodeStats): ReportingSinkEvent[];
|
|
850
850
|
declare function createRunResultEvents(session: SinkSessionMetadata, result: LoadStrikeRunResult): ReportingSinkEvent[];
|
|
851
851
|
declare function createReportingEvent(session: SinkSessionMetadata, occurredUtc: Date, eventType: string, scenarioName: string | null, stepName: string | null, tags: Record<string, string>, fields: Record<string, unknown>): ReportingSinkEvent;
|
|
852
|
+
declare function createIterationObservationEvents(session: SinkSessionMetadata, batch: LoadStrikeIterationObservationBatchV1): ReportingSinkEvent[];
|
|
853
|
+
declare function createIterationCompletionEvents(session: SinkSessionMetadata, completion: LoadStrikeIterationObservationStreamCompletionV1): ReportingSinkEvent[];
|
|
854
|
+
declare function portalEventPayload(event: ReportingSinkEvent, index: number): Record<string, unknown>;
|
|
852
855
|
declare function toOtelAnyValue(value: unknown): Record<string, unknown>;
|
|
853
856
|
declare function sinkSessionMetadataFromContext(context: LoadStrikeBaseContext, session?: LoadStrikeSessionStartInfo, options?: {
|
|
854
857
|
distinctRunIdFallback?: boolean;
|
|
@@ -897,6 +900,8 @@ export declare const __loadstrikeTestExports: {
|
|
|
897
900
|
cloneScenarioStats: typeof cloneScenarioStats;
|
|
898
901
|
cloneSessionStartInfo: typeof cloneSessionStartInfo;
|
|
899
902
|
createFinalStatsEvents: typeof createFinalStatsEvents;
|
|
903
|
+
createIterationCompletionEvents: typeof createIterationCompletionEvents;
|
|
904
|
+
createIterationObservationEvents: typeof createIterationObservationEvents;
|
|
900
905
|
createRealtimeStatsEvents: typeof createRealtimeStatsEvents;
|
|
901
906
|
createRunResultEvents: typeof createRunResultEvents;
|
|
902
907
|
createReportingEvent: typeof createReportingEvent;
|
|
@@ -913,6 +918,7 @@ export declare const __loadstrikeTestExports: {
|
|
|
913
918
|
normalizeStringMap: typeof normalizeStringMap;
|
|
914
919
|
optionNumber: typeof optionNumber;
|
|
915
920
|
optionString: typeof optionString;
|
|
921
|
+
portalEventPayload: typeof portalEventPayload;
|
|
916
922
|
pickBooleanValue: typeof pickBooleanValue;
|
|
917
923
|
pickRecordValue: typeof pickRecordValue;
|
|
918
924
|
postWithTimeout: typeof postWithTimeout;
|
|
@@ -1072,6 +1072,8 @@ declare function validateTrackingSelectorPath(expression: string, fieldName: "Tr
|
|
|
1072
1072
|
declare function validateHttpEndpoint(endpoint: EndpointDefinition, mode: EndpointMode, hasModeDelegate: boolean): void;
|
|
1073
1073
|
declare function applyHttpAuthHeaders(headers: Record<string, string>, options: HttpEndpointOptions, resolveOAuthToken: () => Promise<string>): Promise<void>;
|
|
1074
1074
|
declare function buildHttpRequestBody(body: unknown, bodyType: NonNullable<HttpEndpointOptions["bodyType"]>, contentType: string): unknown;
|
|
1075
|
+
declare function extractJsonPath(value: unknown, arrayPath?: string): unknown;
|
|
1076
|
+
declare function headersToRecord(value: Headers): Record<string, string>;
|
|
1075
1077
|
declare function partitionFromKey(value: string, partitionCount: number): string;
|
|
1076
1078
|
declare function attachPayloadHelpers(payload: TrackingPayload): TrackingPayload;
|
|
1077
1079
|
declare function attachDotNetTrackingPayloadAliases<T extends TrackingPayload>(payload: T): T;
|
|
@@ -1142,8 +1144,10 @@ export declare const __loadstrikeTestExports: {
|
|
|
1142
1144
|
createNatsHeaders: typeof createNatsHeaders;
|
|
1143
1145
|
createRedisStreamPayload: typeof createRedisStreamPayload;
|
|
1144
1146
|
deserializeBrokerPayloadBody: typeof deserializeBrokerPayloadBody;
|
|
1147
|
+
extractJsonPath: typeof extractJsonPath;
|
|
1145
1148
|
extractTrackingValue: typeof extractTrackingValue;
|
|
1146
1149
|
fromKafkaHeaders: typeof fromKafkaHeaders;
|
|
1150
|
+
headersToRecord: typeof headersToRecord;
|
|
1147
1151
|
headerValue: typeof headerValue;
|
|
1148
1152
|
attachDotNetTrackingPayloadAliases: typeof attachDotNetTrackingPayloadAliases;
|
|
1149
1153
|
attachPayloadHelpers: typeof attachPayloadHelpers;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loadstrike/loadstrike-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.31001",
|
|
4
4
|
"description": "TypeScript and JavaScript SDK for in-process load execution, traffic correlation, and reporting.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"load-testing",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"typescript": "^5.9.2"
|
|
82
82
|
},
|
|
83
83
|
"overrides": {
|
|
84
|
-
"brace-expansion": "5.0.
|
|
84
|
+
"brace-expansion": "5.0.8",
|
|
85
85
|
"tar": "7.5.21"
|
|
86
86
|
}
|
|
87
87
|
}
|