@bitfab/sdk 0.61.3 → 0.61.4
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/dist/{chunk-TZSTKVZF.js → chunk-2ZZBNQVA.js} +6 -6
- package/dist/{chunk-TZSTKVZF.js.map → chunk-2ZZBNQVA.js.map} +1 -1
- package/dist/{chunk-7BU5LUMR.js → chunk-3GSEECNP.js} +26 -2
- package/dist/{chunk-7BU5LUMR.js.map → chunk-3GSEECNP.js.map} +1 -1
- package/dist/{chunk-OWXP3YXA.js → chunk-BRSQCK3F.js} +21 -3
- package/dist/chunk-BRSQCK3F.js.map +1 -0
- package/dist/{chunk-34UFYAE6.js → chunk-FL6BNPSO.js} +176 -10
- package/dist/chunk-FL6BNPSO.js.map +1 -0
- package/dist/{chunk-65QKCHI2.js → chunk-THGHTVB6.js} +27 -2
- package/dist/chunk-THGHTVB6.js.map +1 -0
- package/dist/{http-VTIALK4P.js → http-J2DYDKWJ.js} +2 -2
- package/dist/{http-ZWENQXQA.js → http-RH3SW32W.js} +2 -2
- package/dist/index.cjs +214 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +3 -3
- package/dist/node.cjs +214 -9
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -3
- package/dist/{replay-YQG3OGDN.js → replay-CZWFAAIY.js} +3 -3
- package/dist/replayCli.js +2 -2
- package/dist/seedCli.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-34UFYAE6.js.map +0 -1
- package/dist/chunk-65QKCHI2.js.map +0 -1
- package/dist/chunk-OWXP3YXA.js.map +0 -1
- /package/dist/{http-VTIALK4P.js.map → http-J2DYDKWJ.js.map} +0 -0
- /package/dist/{http-ZWENQXQA.js.map → http-RH3SW32W.js.map} +0 -0
- /package/dist/{replay-YQG3OGDN.js.map → replay-CZWFAAIY.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -42,7 +42,7 @@ var __version__, __packageName__;
|
|
|
42
42
|
var init_version_generated = __esm({
|
|
43
43
|
"src/version.generated.ts"() {
|
|
44
44
|
"use strict";
|
|
45
|
-
__version__ = "0.61.
|
|
45
|
+
__version__ = "0.61.4";
|
|
46
46
|
__packageName__ = "@bitfab/sdk";
|
|
47
47
|
}
|
|
48
48
|
});
|
|
@@ -2277,7 +2277,7 @@ function warnDroppedExternalTrace(error) {
|
|
|
2277
2277
|
function experimentIdWireFields(experimentId) {
|
|
2278
2278
|
return { experimentId, testRunId: experimentId };
|
|
2279
2279
|
}
|
|
2280
|
-
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, REPLAY_COMPLETE_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, liveHttpClients, carrierSeq, HttpClient;
|
|
2280
|
+
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS, REPLAY_COMPLETE_REQUEST_TIMEOUT_MS, REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS, REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS, OTLP_TRACES_ENDPOINT, RETRYABLE_STATUSES, EXIT_FLUSH_TIMEOUT_MS, DEFAULT_LIFECYCLE_TIMEOUT_MS2, pendingTracePromises, liveHttpClients, carrierSeq, HttpClient;
|
|
2281
2281
|
var init_http = __esm({
|
|
2282
2282
|
"src/http.ts"() {
|
|
2283
2283
|
"use strict";
|
|
@@ -2296,6 +2296,8 @@ var init_http = __esm({
|
|
|
2296
2296
|
init_warnOnce();
|
|
2297
2297
|
REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
2298
2298
|
REPLAY_COMPLETE_REQUEST_TIMEOUT_MS = 12e4;
|
|
2299
|
+
REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS = 5e3;
|
|
2300
|
+
REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS = 5e3;
|
|
2299
2301
|
OTLP_TRACES_ENDPOINT = "/api/sdk/otel/v1/traces";
|
|
2300
2302
|
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
2301
2303
|
EXIT_FLUSH_TIMEOUT_MS = 5e3;
|
|
@@ -3108,6 +3110,28 @@ var init_http = __esm({
|
|
|
3108
3110
|
{ timeout: REPLAY_COMPLETE_REQUEST_TIMEOUT_MS }
|
|
3109
3111
|
);
|
|
3110
3112
|
}
|
|
3113
|
+
async interruptReplay(experimentId) {
|
|
3114
|
+
try {
|
|
3115
|
+
await this.request(
|
|
3116
|
+
"/api/sdk/replay/interrupt",
|
|
3117
|
+
experimentIdWireFields(experimentId),
|
|
3118
|
+
{ timeout: REPLAY_INTERRUPT_REQUEST_TIMEOUT_MS }
|
|
3119
|
+
);
|
|
3120
|
+
return true;
|
|
3121
|
+
} catch {
|
|
3122
|
+
return false;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
async heartbeatReplay(experimentId) {
|
|
3126
|
+
try {
|
|
3127
|
+
await this.request(
|
|
3128
|
+
"/api/sdk/replay/heartbeat",
|
|
3129
|
+
experimentIdWireFields(experimentId),
|
|
3130
|
+
{ timeout: REPLAY_HEARTBEAT_REQUEST_TIMEOUT_MS }
|
|
3131
|
+
);
|
|
3132
|
+
} catch {
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3111
3135
|
async getReseedSource(traceId) {
|
|
3112
3136
|
return this.get(
|
|
3113
3137
|
`/api/sdk/traces/${encodeURIComponent(traceId)}/reseedSource`
|
|
@@ -3611,6 +3635,114 @@ var init_integrationFixture = __esm({
|
|
|
3611
3635
|
}
|
|
3612
3636
|
});
|
|
3613
3637
|
|
|
3638
|
+
// src/replayHeartbeat.ts
|
|
3639
|
+
function startReplayHeartbeat(send) {
|
|
3640
|
+
let stopped = false;
|
|
3641
|
+
let inFlight;
|
|
3642
|
+
const timer = setInterval(() => {
|
|
3643
|
+
if (stopped || inFlight !== void 0) {
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
inFlight = send().catch(() => void 0).finally(() => {
|
|
3647
|
+
inFlight = void 0;
|
|
3648
|
+
});
|
|
3649
|
+
}, REPLAY_HEARTBEAT_INTERVAL_MS);
|
|
3650
|
+
unrefTimer(timer);
|
|
3651
|
+
return {
|
|
3652
|
+
stop: async () => {
|
|
3653
|
+
stopped = true;
|
|
3654
|
+
clearInterval(timer);
|
|
3655
|
+
await inFlight;
|
|
3656
|
+
}
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
var REPLAY_HEARTBEAT_INTERVAL_MS;
|
|
3660
|
+
var init_replayHeartbeat = __esm({
|
|
3661
|
+
"src/replayHeartbeat.ts"() {
|
|
3662
|
+
"use strict";
|
|
3663
|
+
init_unrefTimer();
|
|
3664
|
+
REPLAY_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
3665
|
+
}
|
|
3666
|
+
});
|
|
3667
|
+
|
|
3668
|
+
// src/replayInterrupt.ts
|
|
3669
|
+
function signalCapableProcess() {
|
|
3670
|
+
if (typeof process === "undefined" || typeof process.on !== "function") {
|
|
3671
|
+
return void 0;
|
|
3672
|
+
}
|
|
3673
|
+
return process;
|
|
3674
|
+
}
|
|
3675
|
+
function describeReplayInterrupt(experimentId, markedInterrupted) {
|
|
3676
|
+
if (markedInterrupted) {
|
|
3677
|
+
return `[replay] Experiment ${experimentId} interrupted.`;
|
|
3678
|
+
}
|
|
3679
|
+
return `[replay] Experiment ${experimentId} stopped, but Bitfab could not mark it interrupted. It will be marked interrupted within 15 minutes. A resume in the next two minutes may be refused as still running.`;
|
|
3680
|
+
}
|
|
3681
|
+
function reportInterrupted(experimentId, markedInterrupted) {
|
|
3682
|
+
try {
|
|
3683
|
+
console.error(describeReplayInterrupt(experimentId, markedInterrupted));
|
|
3684
|
+
} catch {
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
function installReplayInterruptHandlers(target) {
|
|
3688
|
+
const host = signalCapableProcess();
|
|
3689
|
+
if (host === void 0) {
|
|
3690
|
+
return { remove: () => {
|
|
3691
|
+
}, interrupted: () => false };
|
|
3692
|
+
}
|
|
3693
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
3694
|
+
const removeHandlers = () => {
|
|
3695
|
+
for (const [signal, handler] of handlers) {
|
|
3696
|
+
host.removeListener(signal, handler);
|
|
3697
|
+
}
|
|
3698
|
+
handlers.clear();
|
|
3699
|
+
};
|
|
3700
|
+
let interrupting = false;
|
|
3701
|
+
const handleSignal = async (signal) => {
|
|
3702
|
+
const hostHandlesSignal = host.listenerCount(signal) > 1;
|
|
3703
|
+
const markedInterrupted = await target.interrupt();
|
|
3704
|
+
removeHandlers();
|
|
3705
|
+
if (target.onInterrupt !== void 0) {
|
|
3706
|
+
try {
|
|
3707
|
+
target.onInterrupt({
|
|
3708
|
+
experimentId: target.experimentId,
|
|
3709
|
+
experimentUrl: target.experimentUrl,
|
|
3710
|
+
signal,
|
|
3711
|
+
markedInterrupted
|
|
3712
|
+
});
|
|
3713
|
+
} catch {
|
|
3714
|
+
}
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
reportInterrupted(target.experimentId, markedInterrupted);
|
|
3718
|
+
if (!hostHandlesSignal) {
|
|
3719
|
+
host.kill(host.pid, signal);
|
|
3720
|
+
}
|
|
3721
|
+
};
|
|
3722
|
+
for (const signal of REPLAY_INTERRUPT_SIGNALS) {
|
|
3723
|
+
const handler = () => {
|
|
3724
|
+
if (interrupting) {
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
interrupting = true;
|
|
3728
|
+
void handleSignal(signal);
|
|
3729
|
+
};
|
|
3730
|
+
handlers.set(signal, handler);
|
|
3731
|
+
host.on(signal, handler);
|
|
3732
|
+
}
|
|
3733
|
+
return { remove: removeHandlers, interrupted: () => interrupting };
|
|
3734
|
+
}
|
|
3735
|
+
var REPLAY_INTERRUPT_SIGNALS;
|
|
3736
|
+
var init_replayInterrupt = __esm({
|
|
3737
|
+
"src/replayInterrupt.ts"() {
|
|
3738
|
+
"use strict";
|
|
3739
|
+
REPLAY_INTERRUPT_SIGNALS = [
|
|
3740
|
+
"SIGINT",
|
|
3741
|
+
"SIGTERM"
|
|
3742
|
+
];
|
|
3743
|
+
}
|
|
3744
|
+
});
|
|
3745
|
+
|
|
3614
3746
|
// src/selectiveReplay.ts
|
|
3615
3747
|
function replayNodeKey(node) {
|
|
3616
3748
|
return JSON.stringify([node.traceFunctionKey, node.spanName]);
|
|
@@ -4520,7 +4652,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4520
4652
|
const includeDbBranchLease = dbBranchEnabled(options?.dbBranch) && options?.dryRun !== true;
|
|
4521
4653
|
let session;
|
|
4522
4654
|
if (options?.resume === void 0) {
|
|
4523
|
-
const
|
|
4655
|
+
const started = await httpClient.startReplay(
|
|
4524
4656
|
traceFunctionKey,
|
|
4525
4657
|
// limit is meaningless with explicit traceIds (the ID list determines
|
|
4526
4658
|
// the count), so it's omitted from the request entirely.
|
|
@@ -4543,7 +4675,7 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4543
4675
|
options?.judgeAssertions,
|
|
4544
4676
|
options?.experimentalSelectiveReplay
|
|
4545
4677
|
);
|
|
4546
|
-
if (
|
|
4678
|
+
if (started.items.length === 0) {
|
|
4547
4679
|
try {
|
|
4548
4680
|
console.warn(
|
|
4549
4681
|
`Bitfab: no traces matched "${traceFunctionKey}", so this replay ran nothing. Capture a trace, or seed one with seedTrace, before replaying.`
|
|
@@ -4552,10 +4684,10 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4552
4684
|
}
|
|
4553
4685
|
}
|
|
4554
4686
|
session = {
|
|
4555
|
-
experimentId:
|
|
4556
|
-
experimentUrl:
|
|
4687
|
+
experimentId: started.experimentId,
|
|
4688
|
+
experimentUrl: started.experimentUrl,
|
|
4557
4689
|
attempts: requestedAttempts,
|
|
4558
|
-
work: expandReplayWork(
|
|
4690
|
+
work: expandReplayWork(started.items, requestedAttempts),
|
|
4559
4691
|
finished: [],
|
|
4560
4692
|
includeDbBranchLease,
|
|
4561
4693
|
dbBranchSettings: resolveDbBranchSettings(options?.dbBranch)
|
|
@@ -4570,9 +4702,68 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4570
4702
|
options.dbBranch
|
|
4571
4703
|
);
|
|
4572
4704
|
}
|
|
4573
|
-
const { experimentId
|
|
4705
|
+
const { experimentId } = session;
|
|
4706
|
+
const fullExperimentUrl = `${serviceUrl}${session.experimentUrl}`;
|
|
4707
|
+
const heartbeat = startReplayHeartbeat(
|
|
4708
|
+
() => httpClient.heartbeatReplay(experimentId)
|
|
4709
|
+
);
|
|
4710
|
+
const interruptReplay = async () => {
|
|
4711
|
+
await heartbeat.stop();
|
|
4712
|
+
return httpClient.interruptReplay(experimentId);
|
|
4713
|
+
};
|
|
4714
|
+
const interruptHandlers = installReplayInterruptHandlers({
|
|
4715
|
+
experimentId,
|
|
4716
|
+
experimentUrl: fullExperimentUrl,
|
|
4717
|
+
interrupt: interruptReplay,
|
|
4718
|
+
onInterrupt: options?.onInterrupt
|
|
4719
|
+
});
|
|
4720
|
+
let completeReturned = false;
|
|
4721
|
+
const markCompleteReturned = () => {
|
|
4722
|
+
completeReturned = true;
|
|
4723
|
+
void heartbeat.stop();
|
|
4724
|
+
};
|
|
4725
|
+
try {
|
|
4726
|
+
return await runReplaySession(
|
|
4727
|
+
httpClient,
|
|
4728
|
+
fn,
|
|
4729
|
+
session,
|
|
4730
|
+
fullExperimentUrl,
|
|
4731
|
+
maxConcurrency,
|
|
4732
|
+
interruptHandlers.remove,
|
|
4733
|
+
markCompleteReturned,
|
|
4734
|
+
interruptHandlers.interrupted,
|
|
4735
|
+
options,
|
|
4736
|
+
registeredOverrides
|
|
4737
|
+
);
|
|
4738
|
+
} catch (error) {
|
|
4739
|
+
if (!completeReturned && !interruptHandlers.interrupted()) {
|
|
4740
|
+
await interruptReplay();
|
|
4741
|
+
}
|
|
4742
|
+
throw error;
|
|
4743
|
+
} finally {
|
|
4744
|
+
interruptHandlers.remove();
|
|
4745
|
+
await heartbeat.stop();
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
function throwIfInterrupted(interrupted, items, experimentId, experimentUrl) {
|
|
4749
|
+
if (!interrupted()) {
|
|
4750
|
+
return;
|
|
4751
|
+
}
|
|
4752
|
+
const cause = new BitfabError(
|
|
4753
|
+
`Experiment ${experimentId} was interrupted, so it was not completed or graded. Resume it by passing { resume: "${experimentId}" } to replay, or with bitfab-replay --resume ${experimentId}.`,
|
|
4754
|
+
experimentUrl
|
|
4755
|
+
);
|
|
4756
|
+
throw new ReplayError(
|
|
4757
|
+
cause.message,
|
|
4758
|
+
items,
|
|
4759
|
+
experimentId,
|
|
4760
|
+
experimentUrl,
|
|
4761
|
+
cause
|
|
4762
|
+
);
|
|
4763
|
+
}
|
|
4764
|
+
async function runReplaySession(httpClient, fn, session, fullExperimentUrl, maxConcurrency, stopInterruptingOnSignal, markCompleteReturned, interrupted, options, registeredOverrides) {
|
|
4765
|
+
const { experimentId, attempts } = session;
|
|
4574
4766
|
const mockStrategy = options?.mock ?? "marked";
|
|
4575
|
-
const fullExperimentUrl = `${serviceUrl}${experimentUrl}`;
|
|
4576
4767
|
try {
|
|
4577
4768
|
options?.onExperimentStart?.({
|
|
4578
4769
|
experimentId,
|
|
@@ -4722,7 +4913,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4722
4913
|
);
|
|
4723
4914
|
const resultItems = [...carriedOverItems, ...ranItems];
|
|
4724
4915
|
if (options?.dryRun === true) {
|
|
4916
|
+
throwIfInterrupted(
|
|
4917
|
+
interrupted,
|
|
4918
|
+
resultItems,
|
|
4919
|
+
experimentId,
|
|
4920
|
+
fullExperimentUrl
|
|
4921
|
+
);
|
|
4922
|
+
stopInterruptingOnSignal();
|
|
4725
4923
|
await httpClient.completeReplay(experimentId).catch(() => void 0);
|
|
4924
|
+
markCompleteReturned();
|
|
4726
4925
|
const dryResult = {
|
|
4727
4926
|
items: resultItems,
|
|
4728
4927
|
experimentId,
|
|
@@ -4747,12 +4946,15 @@ async function replay(httpClient, serviceUrl, traceFunctionKey, fn, options, reg
|
|
|
4747
4946
|
ranItems[index].traceId = readBack;
|
|
4748
4947
|
}
|
|
4749
4948
|
}
|
|
4949
|
+
throwIfInterrupted(interrupted, resultItems, experimentId, fullExperimentUrl);
|
|
4950
|
+
stopInterruptingOnSignal();
|
|
4750
4951
|
const completeResult = await preserveReplayFailure(
|
|
4751
4952
|
() => httpClient.completeReplay(experimentId),
|
|
4752
4953
|
resultItems,
|
|
4753
4954
|
experimentId,
|
|
4754
4955
|
fullExperimentUrl
|
|
4755
4956
|
);
|
|
4957
|
+
markCompleteReturned();
|
|
4756
4958
|
const serverTraceIds = completeResult.traceIds;
|
|
4757
4959
|
const replayTokens = completeResult.tokens;
|
|
4758
4960
|
if (serverTraceIds !== void 0) {
|
|
@@ -4856,6 +5058,8 @@ var init_replay = __esm({
|
|
|
4856
5058
|
init_mockOverride();
|
|
4857
5059
|
init_randomUuid();
|
|
4858
5060
|
init_replayContext();
|
|
5061
|
+
init_replayHeartbeat();
|
|
5062
|
+
init_replayInterrupt();
|
|
4859
5063
|
init_selectiveReplayRuntime();
|
|
4860
5064
|
init_serialize();
|
|
4861
5065
|
REPLAY_PERSISTENCE_TIMEOUT_MS = 3e4;
|
|
@@ -11632,6 +11836,7 @@ init_replay();
|
|
|
11632
11836
|
// src/replayRegistry.ts
|
|
11633
11837
|
init_errors();
|
|
11634
11838
|
init_replay();
|
|
11839
|
+
init_replayInterrupt();
|
|
11635
11840
|
function defineReplayRegistry(registry) {
|
|
11636
11841
|
return registry;
|
|
11637
11842
|
}
|