@loadstrike/loadstrike-sdk 1.0.30401 → 1.0.31601
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 +6 -0
- package/dist/cjs/iteration-observation-diagnostics.js +513 -0
- package/dist/cjs/iteration-observations.js +195 -22
- package/dist/cjs/local-report-input.js +21 -0
- package/dist/cjs/local.js +55 -10
- package/dist/cjs/report-history.js +421 -0
- package/dist/cjs/reporting-svg.js +116 -0
- package/dist/cjs/reporting.js +436 -140
- package/dist/cjs/runtime.js +313 -85
- 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-report-input.js +17 -0
- package/dist/esm/local.js +55 -10
- package/dist/esm/report-history.js +413 -0
- package/dist/esm/reporting-svg.js +113 -0
- package/dist/esm/reporting.js +436 -140
- package/dist/esm/runtime.js +313 -85
- 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/local-report-input.d.ts +6 -0
- package/dist/types/report-history.d.ts +124 -0
- package/dist/types/reporting-svg.d.ts +2 -0
- package/dist/types/reporting.d.ts +6 -3
- package/dist/types/runtime.d.ts +25 -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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { performance } from "node:perf_hooks";
|
|
2
3
|
import { gzipSync } from "node:zlib";
|
|
4
|
+
import { logIterationObservationFailure, logIterationObservationRecovery } from "./iteration-observation-diagnostics.js";
|
|
5
|
+
import { cooperativeSinkRetryYield, normalizeSinkRetryBackoffMs, normalizeSinkRetryCount, sinkRetryDelayMs, waitForSinkRetryDelay } from "./sink-retry-policy.js";
|
|
3
6
|
export const ITERATION_OBSERVATION_SCHEMA_VERSION = "loadstrike.iteration-observation/1";
|
|
4
7
|
export const ITERATION_OBSERVATION_BATCH_SCHEMA_VERSION = "loadstrike.iteration-batch/1";
|
|
5
8
|
export const ITERATION_OBSERVATION_STREAM_COMPLETION_SCHEMA_VERSION = "loadstrike.iteration-stream-completion/1";
|
|
@@ -141,10 +144,15 @@ export class ProcessIterationObservationBuffer {
|
|
|
141
144
|
}
|
|
142
145
|
const processIterationObservationBuffer = new ProcessIterationObservationBuffer();
|
|
143
146
|
class SinkDispatcher {
|
|
144
|
-
constructor(target, depth, parallelism, onDropped) {
|
|
147
|
+
constructor(target, depth, parallelism, retryCount, retryBackoffMs, logger, runId, resultOwnerId, onDropped) {
|
|
145
148
|
this.target = target;
|
|
146
149
|
this.depth = depth;
|
|
147
150
|
this.parallelism = parallelism;
|
|
151
|
+
this.retryCount = retryCount;
|
|
152
|
+
this.retryBackoffMs = retryBackoffMs;
|
|
153
|
+
this.logger = logger;
|
|
154
|
+
this.runId = runId;
|
|
155
|
+
this.resultOwnerId = resultOwnerId;
|
|
148
156
|
this.onDropped = onDropped;
|
|
149
157
|
this.queue = [];
|
|
150
158
|
this.active = new Set();
|
|
@@ -152,6 +160,8 @@ class SinkDispatcher {
|
|
|
152
160
|
this.timedOut = false;
|
|
153
161
|
this.delivered = 0n;
|
|
154
162
|
this.dropped = 0n;
|
|
163
|
+
this.drainDeadlineMs = null;
|
|
164
|
+
this.pendingRetryDelays = new Set();
|
|
155
165
|
}
|
|
156
166
|
get deliveredCount() {
|
|
157
167
|
return this.delivered;
|
|
@@ -160,7 +170,7 @@ class SinkDispatcher {
|
|
|
160
170
|
return this.dropped;
|
|
161
171
|
}
|
|
162
172
|
get canPublishCompletion() {
|
|
163
|
-
return this.queue.length === 0 && this.active.size === 0;
|
|
173
|
+
return !this.timedOut && this.queue.length === 0 && this.active.size === 0;
|
|
164
174
|
}
|
|
165
175
|
enqueue(batch) {
|
|
166
176
|
if (this.timedOut || this.queue.length >= this.depth) {
|
|
@@ -188,7 +198,6 @@ class SinkDispatcher {
|
|
|
188
198
|
});
|
|
189
199
|
const timeout = new Promise((resolve) => {
|
|
190
200
|
timer = setTimeout(() => resolve(false), Math.max(timeoutMs, 1));
|
|
191
|
-
timer.unref?.();
|
|
192
201
|
});
|
|
193
202
|
const completed = await Promise.race([idle, timeout]);
|
|
194
203
|
if (timer) {
|
|
@@ -201,16 +210,30 @@ class SinkDispatcher {
|
|
|
201
210
|
}
|
|
202
211
|
return completed;
|
|
203
212
|
}
|
|
213
|
+
beginDrain(deadlineMs) {
|
|
214
|
+
this.drainDeadlineMs = deadlineMs;
|
|
215
|
+
const delaysThatCannotFit = Array.from(this.pendingRetryDelays)
|
|
216
|
+
.filter((pending) => pending.dueAtMs >= deadlineMs);
|
|
217
|
+
if (delaysThatCannotFit.length > 0) {
|
|
218
|
+
for (const pending of delaysThatCannotFit) {
|
|
219
|
+
pending.cancel();
|
|
220
|
+
}
|
|
221
|
+
this.timeoutOutstanding();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
204
224
|
pump() {
|
|
205
225
|
while (!this.timedOut && this.active.size < this.parallelism && this.queue.length > 0) {
|
|
206
226
|
const item = this.queue.shift();
|
|
207
227
|
this.active.add(item);
|
|
208
228
|
Promise.resolve()
|
|
209
|
-
.then(() => this.
|
|
210
|
-
.then(() => {
|
|
211
|
-
if (!this.timedOut) {
|
|
229
|
+
.then(() => this.deliver(item))
|
|
230
|
+
.then((outcome) => {
|
|
231
|
+
if (outcome === "success" && !this.timedOut) {
|
|
212
232
|
this.delivered += item.count;
|
|
213
233
|
}
|
|
234
|
+
else if (outcome === "exhausted" && !this.timedOut) {
|
|
235
|
+
this.drop("observation_sink_delivery_failed", item.batch.observations);
|
|
236
|
+
}
|
|
214
237
|
})
|
|
215
238
|
.catch(() => {
|
|
216
239
|
if (!this.timedOut) {
|
|
@@ -225,10 +248,95 @@ class SinkDispatcher {
|
|
|
225
248
|
}
|
|
226
249
|
this.notifyIdle();
|
|
227
250
|
}
|
|
251
|
+
async deliver(item) {
|
|
252
|
+
const maximumAttempts = this.retryCount + 1;
|
|
253
|
+
for (let attempt = 1; attempt <= maximumAttempts; attempt += 1) {
|
|
254
|
+
if (this.timedOut || this.deadlineExpired()) {
|
|
255
|
+
return "deadline";
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
await this.target.saveIterationBatch(item.batch);
|
|
259
|
+
if (attempt > 1) {
|
|
260
|
+
logIterationObservationRecovery(this.logger, {
|
|
261
|
+
...this.diagnosticContext(item, attempt, maximumAttempts),
|
|
262
|
+
nextDelayMs: 0
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return "success";
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
const exhausted = attempt >= maximumAttempts;
|
|
269
|
+
const nextDelayMs = exhausted
|
|
270
|
+
? 0
|
|
271
|
+
: sinkRetryDelayMs(this.retryBackoffMs, attempt);
|
|
272
|
+
logIterationObservationFailure(this.logger, exhausted ? "error" : "warn", {
|
|
273
|
+
...this.diagnosticContext(item, attempt, maximumAttempts),
|
|
274
|
+
nextDelayMs
|
|
275
|
+
}, error);
|
|
276
|
+
if (exhausted) {
|
|
277
|
+
return "exhausted";
|
|
278
|
+
}
|
|
279
|
+
if (!await this.waitForRetry(attempt)) {
|
|
280
|
+
this.timeoutOutstanding();
|
|
281
|
+
return "deadline";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return "exhausted";
|
|
286
|
+
}
|
|
287
|
+
diagnosticContext(item, attempt, maximumAttempts) {
|
|
288
|
+
return {
|
|
289
|
+
sinkName: this.target.name,
|
|
290
|
+
operation: "observation-batch",
|
|
291
|
+
runId: this.runId,
|
|
292
|
+
resultOwnerId: this.resultOwnerId,
|
|
293
|
+
batchId: item.batch.batchId,
|
|
294
|
+
batchSequence64: item.batch.batchSequence64,
|
|
295
|
+
observationCount: Number(item.count),
|
|
296
|
+
attempt,
|
|
297
|
+
maximumAttempts
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
async waitForRetry(retryNumber) {
|
|
301
|
+
const delayMs = sinkRetryDelayMs(this.retryBackoffMs, retryNumber);
|
|
302
|
+
const deadline = this.drainDeadlineMs;
|
|
303
|
+
if (deadline !== null && !delayCanFit(delayMs, deadline)) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
if (delayMs === 0) {
|
|
307
|
+
await cooperativeSinkRetryYield();
|
|
308
|
+
return !this.timedOut && !this.deadlineExpired();
|
|
309
|
+
}
|
|
310
|
+
return await new Promise((resolve) => {
|
|
311
|
+
let settled = false;
|
|
312
|
+
let pending;
|
|
313
|
+
const finish = (completed) => {
|
|
314
|
+
if (settled)
|
|
315
|
+
return;
|
|
316
|
+
settled = true;
|
|
317
|
+
clearTimeout(timer);
|
|
318
|
+
this.pendingRetryDelays.delete(pending);
|
|
319
|
+
resolve(completed && !this.timedOut && !this.deadlineExpired());
|
|
320
|
+
};
|
|
321
|
+
const timer = setTimeout(() => finish(true), delayMs);
|
|
322
|
+
const cancel = () => finish(false);
|
|
323
|
+
pending = {
|
|
324
|
+
dueAtMs: performance.now() + delayMs,
|
|
325
|
+
cancel
|
|
326
|
+
};
|
|
327
|
+
this.pendingRetryDelays.add(pending);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
deadlineExpired() {
|
|
331
|
+
return this.drainDeadlineMs !== null && performance.now() >= this.drainDeadlineMs;
|
|
332
|
+
}
|
|
228
333
|
timeoutOutstanding() {
|
|
229
334
|
if (this.timedOut || this.isIdle())
|
|
230
335
|
return;
|
|
231
336
|
this.timedOut = true;
|
|
337
|
+
for (const pending of Array.from(this.pendingRetryDelays)) {
|
|
338
|
+
pending.cancel();
|
|
339
|
+
}
|
|
232
340
|
const queued = [...this.queue];
|
|
233
341
|
const active = [...this.active];
|
|
234
342
|
this.queue.length = 0;
|
|
@@ -300,7 +408,7 @@ export class IterationObservationReporter {
|
|
|
300
408
|
this.recordWarning("sink_iteration_batches_unsupported", sink.name, "", -1, 1n, "The reporting sink does not support raw iteration batches.");
|
|
301
409
|
return false;
|
|
302
410
|
})
|
|
303
|
-
.map((target) => new SinkDispatcher(target, this.settings.sinkQueueDepth, this.settings.sinkParallelism, (code, sinkName, observations) => this.recordSinkDrop(code, sinkName, observations)));
|
|
411
|
+
.map((target) => new SinkDispatcher(target, this.settings.sinkQueueDepth, this.settings.sinkParallelism, normalizeSinkRetryCount(options.sinkRetryCount), normalizeSinkRetryBackoffMs(options.sinkRetryBackoffMs), options.logger, options.runId, options.resultOwnerId, (code, sinkName, observations) => this.recordSinkDrop(code, sinkName, observations)));
|
|
304
412
|
if (this.dispatchers.length > 0) {
|
|
305
413
|
this.buffer.register(this.streamKey, this.settings.maxBufferBytes);
|
|
306
414
|
}
|
|
@@ -356,11 +464,14 @@ export class IterationObservationReporter {
|
|
|
356
464
|
this.sealed = true;
|
|
357
465
|
this.clock.clearInterval(this.timer);
|
|
358
466
|
this.flushNow();
|
|
359
|
-
const started =
|
|
467
|
+
const started = performance.now();
|
|
360
468
|
const deadline = started + this.settings.drainTimeoutMs;
|
|
469
|
+
for (const dispatcher of this.dispatchers) {
|
|
470
|
+
dispatcher.beginDrain(deadline);
|
|
471
|
+
}
|
|
361
472
|
const drainResults = new Array(this.dispatchers.length).fill(false);
|
|
362
473
|
const completionResults = await Promise.all(this.dispatchers.map(async (dispatcher, index) => {
|
|
363
|
-
const drained = await dispatcher.waitForIdle(Math.max(deadline -
|
|
474
|
+
const drained = await dispatcher.waitForIdle(Math.max(deadline - performance.now(), 1));
|
|
364
475
|
drainResults[index] = drained;
|
|
365
476
|
if (!dispatcher.canPublishCompletion) {
|
|
366
477
|
return false;
|
|
@@ -375,19 +486,7 @@ export class IterationObservationReporter {
|
|
|
375
486
|
&& dispatcher.droppedCount === 0n
|
|
376
487
|
&& dispatcher.deliveredCount === this.captured;
|
|
377
488
|
const completion = this.createCompletion(dispatcher, sinkReportingComplete);
|
|
378
|
-
|
|
379
|
-
const outcome = await Promise.race([
|
|
380
|
-
Promise.resolve()
|
|
381
|
-
.then(() => complete(completion))
|
|
382
|
-
.then(() => "success", () => "failed"),
|
|
383
|
-
new Promise((resolve) => {
|
|
384
|
-
timer = setTimeout(() => resolve("timeout"), Math.max(deadline - Date.now(), 1));
|
|
385
|
-
timer.unref?.();
|
|
386
|
-
})
|
|
387
|
-
]);
|
|
388
|
-
if (timer) {
|
|
389
|
-
clearTimeout(timer);
|
|
390
|
-
}
|
|
489
|
+
const outcome = await this.completeWithRetries(complete, completion, deadline, dispatcher.target.name);
|
|
391
490
|
if (outcome === "success") {
|
|
392
491
|
return true;
|
|
393
492
|
}
|
|
@@ -423,6 +522,76 @@ export class IterationObservationReporter {
|
|
|
423
522
|
};
|
|
424
523
|
return { ...this.finalResult };
|
|
425
524
|
}
|
|
525
|
+
async completeWithRetries(complete, completion, deadline, sinkName) {
|
|
526
|
+
const retryCount = normalizeSinkRetryCount(this.options.sinkRetryCount);
|
|
527
|
+
const backoffMs = normalizeSinkRetryBackoffMs(this.options.sinkRetryBackoffMs);
|
|
528
|
+
const maximumAttempts = retryCount + 1;
|
|
529
|
+
for (let attempt = 1; attempt <= maximumAttempts; attempt += 1) {
|
|
530
|
+
const remainingMs = deadline - performance.now();
|
|
531
|
+
if (remainingMs <= 0) {
|
|
532
|
+
return "timeout";
|
|
533
|
+
}
|
|
534
|
+
let timer;
|
|
535
|
+
const outcome = await Promise.race([
|
|
536
|
+
Promise.resolve()
|
|
537
|
+
.then(() => complete(completion))
|
|
538
|
+
.then(() => ({ kind: "success" }), (error) => ({ kind: "failed", error })),
|
|
539
|
+
new Promise((resolve) => {
|
|
540
|
+
timer = setTimeout(() => resolve({ kind: "timeout" }), Math.max(remainingMs, 1));
|
|
541
|
+
})
|
|
542
|
+
]);
|
|
543
|
+
if (timer) {
|
|
544
|
+
clearTimeout(timer);
|
|
545
|
+
}
|
|
546
|
+
if (outcome.kind === "success") {
|
|
547
|
+
if (attempt > 1) {
|
|
548
|
+
logIterationObservationRecovery(this.options.logger, {
|
|
549
|
+
sinkName,
|
|
550
|
+
operation: "observation-stream-completion",
|
|
551
|
+
runId: this.options.runId,
|
|
552
|
+
resultOwnerId: this.options.resultOwnerId,
|
|
553
|
+
observationCount: 0,
|
|
554
|
+
attempt,
|
|
555
|
+
maximumAttempts,
|
|
556
|
+
nextDelayMs: 0
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return "success";
|
|
560
|
+
}
|
|
561
|
+
if (outcome.kind === "timeout") {
|
|
562
|
+
return "timeout";
|
|
563
|
+
}
|
|
564
|
+
const exhausted = attempt >= maximumAttempts;
|
|
565
|
+
const nextDelayMs = exhausted ? 0 : sinkRetryDelayMs(backoffMs, attempt);
|
|
566
|
+
logIterationObservationFailure(this.options.logger, exhausted ? "error" : "warn", {
|
|
567
|
+
sinkName,
|
|
568
|
+
operation: "observation-stream-completion",
|
|
569
|
+
runId: this.options.runId,
|
|
570
|
+
resultOwnerId: this.options.resultOwnerId,
|
|
571
|
+
observationCount: 0,
|
|
572
|
+
attempt,
|
|
573
|
+
maximumAttempts,
|
|
574
|
+
nextDelayMs
|
|
575
|
+
}, outcome.error);
|
|
576
|
+
if (exhausted) {
|
|
577
|
+
return "failed";
|
|
578
|
+
}
|
|
579
|
+
const delayMs = nextDelayMs;
|
|
580
|
+
if (!delayCanFit(delayMs, deadline)) {
|
|
581
|
+
return "timeout";
|
|
582
|
+
}
|
|
583
|
+
if (delayMs === 0) {
|
|
584
|
+
await cooperativeSinkRetryYield();
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
await waitForSinkRetryDelay(delayMs);
|
|
588
|
+
}
|
|
589
|
+
if (performance.now() >= deadline) {
|
|
590
|
+
return "timeout";
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return "failed";
|
|
594
|
+
}
|
|
426
595
|
buildWarnings() {
|
|
427
596
|
return Array.from(this.warnings.values())
|
|
428
597
|
.sort((left, right) => left.code.localeCompare(right.code)
|
|
@@ -685,6 +854,10 @@ function truncateUtf8(value, maxBytes) {
|
|
|
685
854
|
}
|
|
686
855
|
return output;
|
|
687
856
|
}
|
|
857
|
+
function delayCanFit(delayMs, deadlineMs) {
|
|
858
|
+
const remainingMs = deadlineMs - performance.now();
|
|
859
|
+
return remainingMs > 0 && delayMs < remainingMs;
|
|
860
|
+
}
|
|
688
861
|
function deepFreezeObservation(observation) {
|
|
689
862
|
for (const step of observation.steps) {
|
|
690
863
|
Object.freeze(step);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function emptyLocalReportInput() {
|
|
2
|
+
return {
|
|
3
|
+
history: {
|
|
4
|
+
status: "available",
|
|
5
|
+
points: []
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function distributedLocalReportInput() {
|
|
10
|
+
return {
|
|
11
|
+
history: {
|
|
12
|
+
status: "unavailable",
|
|
13
|
+
reasonCategory: "distributed_temporal_aggregation_unavailable",
|
|
14
|
+
points: []
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
package/dist/esm/local.js
CHANGED
|
@@ -1378,9 +1378,20 @@ function readTrackingId(payload, selector) {
|
|
|
1378
1378
|
return null;
|
|
1379
1379
|
}
|
|
1380
1380
|
let current = body;
|
|
1381
|
-
|
|
1381
|
+
let segments;
|
|
1382
|
+
try {
|
|
1383
|
+
segments = safeJsonPathSegments(path);
|
|
1384
|
+
}
|
|
1385
|
+
catch {
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
for (const segment of segments) {
|
|
1382
1389
|
if (current && typeof current === "object" && !Array.isArray(current)) {
|
|
1383
|
-
|
|
1390
|
+
const record = current;
|
|
1391
|
+
if (!Object.prototype.hasOwnProperty.call(record, segment)) {
|
|
1392
|
+
return null;
|
|
1393
|
+
}
|
|
1394
|
+
current = record[segment];
|
|
1384
1395
|
}
|
|
1385
1396
|
else {
|
|
1386
1397
|
return null;
|
|
@@ -1413,23 +1424,57 @@ function readOptionalTrackingSelectorValue(value) {
|
|
|
1413
1424
|
return undefined;
|
|
1414
1425
|
}
|
|
1415
1426
|
function setJsonPathValue(body, path, value) {
|
|
1416
|
-
const target =
|
|
1417
|
-
|
|
1418
|
-
: {};
|
|
1419
|
-
const segments = path.split(".").filter(Boolean);
|
|
1427
|
+
const target = cloneJsonRecord(body);
|
|
1428
|
+
const segments = safeJsonPathSegments(path);
|
|
1420
1429
|
if (!segments.length) {
|
|
1421
1430
|
return target;
|
|
1422
1431
|
}
|
|
1423
1432
|
let current = target;
|
|
1424
1433
|
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
1425
1434
|
const segment = segments[i];
|
|
1426
|
-
const next = current
|
|
1435
|
+
const next = readOwnJsonProperty(current, segment);
|
|
1436
|
+
let child;
|
|
1427
1437
|
if (!next || typeof next !== "object" || Array.isArray(next)) {
|
|
1428
|
-
|
|
1438
|
+
child = {};
|
|
1429
1439
|
}
|
|
1430
|
-
|
|
1440
|
+
else {
|
|
1441
|
+
child = cloneJsonRecord(next);
|
|
1442
|
+
}
|
|
1443
|
+
defineJsonProperty(current, segment, child);
|
|
1444
|
+
current = child;
|
|
1445
|
+
}
|
|
1446
|
+
defineJsonProperty(current, segments[segments.length - 1], value);
|
|
1447
|
+
return target;
|
|
1448
|
+
}
|
|
1449
|
+
const FORBIDDEN_JSON_PATH_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
1450
|
+
function safeJsonPathSegments(path) {
|
|
1451
|
+
const segments = path.split(".").filter(Boolean);
|
|
1452
|
+
const forbidden = segments.find((segment) => FORBIDDEN_JSON_PATH_SEGMENTS.has(segment));
|
|
1453
|
+
if (forbidden) {
|
|
1454
|
+
throw new Error(`Tracking selector contains forbidden JSON path segment '${forbidden}'.`);
|
|
1455
|
+
}
|
|
1456
|
+
return segments;
|
|
1457
|
+
}
|
|
1458
|
+
function defineJsonProperty(target, key, value) {
|
|
1459
|
+
Object.defineProperty(target, key, {
|
|
1460
|
+
configurable: true,
|
|
1461
|
+
enumerable: true,
|
|
1462
|
+
value,
|
|
1463
|
+
writable: true
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
function readOwnJsonProperty(target, key) {
|
|
1467
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
1468
|
+
return descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
1469
|
+
}
|
|
1470
|
+
function cloneJsonRecord(value) {
|
|
1471
|
+
const target = {};
|
|
1472
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1473
|
+
return target;
|
|
1474
|
+
}
|
|
1475
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1476
|
+
defineJsonProperty(target, key, entry);
|
|
1431
1477
|
}
|
|
1432
|
-
current[segments[segments.length - 1]] = value;
|
|
1433
1478
|
return target;
|
|
1434
1479
|
}
|
|
1435
1480
|
function mapCorrelationStore(tracking, runNamespace) {
|