@adaptic/utils 0.0.990 → 0.0.992
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/index.cjs +335 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +335 -2
- package/dist/index.mjs.map +1 -1
- package/dist/test.js +85 -0
- package/dist/test.js.map +1 -1
- package/dist/types/alpaca-market-data-api.d.ts +42 -0
- package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
- package/dist/types/index.d.ts +25 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/misc-utils.d.ts +37 -0
- package/dist/types/misc-utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2689,6 +2689,48 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
2689
2689
|
};
|
|
2690
2690
|
reconnectAttempts = {};
|
|
2691
2691
|
reconnectTimers = {};
|
|
2692
|
+
/**
|
|
2693
|
+
* Wall-clock timestamp of the most recent Alpaca app-level error code
|
|
2694
|
+
* 406 ("connection limit exceeded") received on each stream. Used by
|
|
2695
|
+
* {@link scheduleReconnect} to apply a long backoff with jitter rather
|
|
2696
|
+
* than the normal sub-second exponential ramp — without this, hitting
|
|
2697
|
+
* Alpaca's account-wide concurrent-connection cap (typical for blue/green
|
|
2698
|
+
* deploy rollovers where the old pod's WS slots haven't been released
|
|
2699
|
+
* yet) produced a 10-attempt retry storm that compounded the slot
|
|
2700
|
+
* pressure and consumed the per-account connection quota across the
|
|
2701
|
+
* organisation.
|
|
2702
|
+
*
|
|
2703
|
+
* Cleared once the long-backoff retry is scheduled so that subsequent
|
|
2704
|
+
* normal failures fall back to the standard sub-second exponential.
|
|
2705
|
+
*
|
|
2706
|
+
* @see CONNECTION_LIMIT_BACKOFF_MS / CONNECTION_LIMIT_BACKOFF_JITTER_MS
|
|
2707
|
+
*/
|
|
2708
|
+
lastConnectionLimitAt = {};
|
|
2709
|
+
/**
|
|
2710
|
+
* Five-minute base backoff after Alpaca's app-level 406. Long enough
|
|
2711
|
+
* for Alpaca's server-side cleanup to release stale slots in typical
|
|
2712
|
+
* rollover scenarios; short enough that an operator doesn't need to
|
|
2713
|
+
* intervene. Mirrors the equivalent MassiveClient
|
|
2714
|
+
* `MAX_CONNECTIONS_RETRY_DELAY_MS` (engine v1.0.59) so both providers
|
|
2715
|
+
* behave identically under the same failure mode.
|
|
2716
|
+
*/
|
|
2717
|
+
CONNECTION_LIMIT_BACKOFF_MS = 5 * 60_000;
|
|
2718
|
+
/**
|
|
2719
|
+
* ±30 s of uniform jitter on the connection-limit backoff. Prevents
|
|
2720
|
+
* a thundering-herd retry when all three streams (stock / option /
|
|
2721
|
+
* crypto) hit 406 simultaneously during a deploy rollover — without
|
|
2722
|
+
* jitter they'd all retry at the same wall-clock instant and could
|
|
2723
|
+
* re-trip the account cap together.
|
|
2724
|
+
*/
|
|
2725
|
+
CONNECTION_LIMIT_BACKOFF_JITTER_MS = 30_000;
|
|
2726
|
+
/**
|
|
2727
|
+
* Recency window within which a 406 is considered "still applicable"
|
|
2728
|
+
* to a subsequent reconnect-schedule call. The 406 message handler
|
|
2729
|
+
* stamps {@link lastConnectionLimitAt} and the `close` handler fires
|
|
2730
|
+
* shortly afterwards (sub-second typically) — the window is wide
|
|
2731
|
+
* enough to absorb scheduling delays without false-positives.
|
|
2732
|
+
*/
|
|
2733
|
+
CONNECTION_LIMIT_RECENCY_MS = 30_000;
|
|
2692
2734
|
setMode(mode = "production") {
|
|
2693
2735
|
if (mode === "sandbox") {
|
|
2694
2736
|
// sandbox mode
|
|
@@ -2820,6 +2862,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
2820
2862
|
}
|
|
2821
2863
|
else if (message.T === "error") {
|
|
2822
2864
|
log$l(`${streamType} stream error: ${message.msg} (code: ${message.code}, raw: ${JSON.stringify(message)})`, { type: "error" });
|
|
2865
|
+
// Alpaca code 406: "connection limit exceeded" — account-wide
|
|
2866
|
+
// concurrent-WS cap reached. The Alpaca server will close the
|
|
2867
|
+
// socket immediately after this frame, which would normally
|
|
2868
|
+
// trigger our standard sub-second exponential reconnect chain
|
|
2869
|
+
// (1 s, 2 s, 4 s, 8 s, 16 s, 30 s × 5) — exactly the wrong
|
|
2870
|
+
// behaviour against a rate-limit response. Stamp the recency
|
|
2871
|
+
// marker so {@link scheduleReconnect} switches to the
|
|
2872
|
+
// 5-minute jittered backoff instead.
|
|
2873
|
+
if (typeof message.code === "number" &&
|
|
2874
|
+
message.code === 406) {
|
|
2875
|
+
this.lastConnectionLimitAt[streamType] = Date.now();
|
|
2876
|
+
}
|
|
2823
2877
|
}
|
|
2824
2878
|
else if (message.S) {
|
|
2825
2879
|
super.emit(`${streamType}-${message.T}`, message);
|
|
@@ -2853,6 +2907,37 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
2853
2907
|
});
|
|
2854
2908
|
}
|
|
2855
2909
|
scheduleReconnect(streamType) {
|
|
2910
|
+
// 406-recovery fast path. When the most recent close was preceded
|
|
2911
|
+
// by an Alpaca app-level 406 ("connection limit exceeded"), the
|
|
2912
|
+
// standard sub-second exponential ramp is exactly wrong — it
|
|
2913
|
+
// hammers the rate-limit endpoint and prolongs the slot pressure.
|
|
2914
|
+
// Use a 5-minute jittered backoff instead and reset the normal
|
|
2915
|
+
// attempt counter so we don't fall off the end of maxAttempts
|
|
2916
|
+
// and permanently give up on a transient rollover blip.
|
|
2917
|
+
const connectionLimitAt = this.lastConnectionLimitAt[streamType];
|
|
2918
|
+
const isRecentConnectionLimit = typeof connectionLimitAt === "number" &&
|
|
2919
|
+
Date.now() - connectionLimitAt <= this.CONNECTION_LIMIT_RECENCY_MS;
|
|
2920
|
+
if (isRecentConnectionLimit) {
|
|
2921
|
+
const jitter = Math.floor((Math.random() - 0.5) *
|
|
2922
|
+
2 *
|
|
2923
|
+
this.CONNECTION_LIMIT_BACKOFF_JITTER_MS);
|
|
2924
|
+
const delayMs = this.CONNECTION_LIMIT_BACKOFF_MS + jitter;
|
|
2925
|
+
// Reset normal attempt counter so the next 406 retry doesn't
|
|
2926
|
+
// inherit a stale exponential cap.
|
|
2927
|
+
this.reconnectAttempts[streamType] = 0;
|
|
2928
|
+
// Consume the recency marker — subsequent reconnects fall back
|
|
2929
|
+
// to the standard exponential path unless a new 406 arrives.
|
|
2930
|
+
delete this.lastConnectionLimitAt[streamType];
|
|
2931
|
+
log$l(`${streamType} stream: Alpaca 406 connection-limit recovery — backing off ${Math.round(delayMs / 1000)}s before retry to allow account-wide slot release`, { type: "warn" });
|
|
2932
|
+
if (this.reconnectTimers[streamType]) {
|
|
2933
|
+
clearTimeout(this.reconnectTimers[streamType]);
|
|
2934
|
+
}
|
|
2935
|
+
this.reconnectTimers[streamType] = setTimeout(() => {
|
|
2936
|
+
log$l(`${streamType} stream: attempting reconnect after 406-recovery backoff`, { type: "info" });
|
|
2937
|
+
this.connect(streamType);
|
|
2938
|
+
}, delayMs);
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2856
2941
|
const attempts = this.reconnectAttempts[streamType] ?? 0;
|
|
2857
2942
|
const maxAttempts = 10;
|
|
2858
2943
|
if (attempts >= maxAttempts) {
|
|
@@ -6042,6 +6127,202 @@ async function createLimitOrder(auth, params = {
|
|
|
6042
6127
|
}
|
|
6043
6128
|
|
|
6044
6129
|
// Utility function for debug logging
|
|
6130
|
+
/** Sliding-window size (number of recent outcomes tracked per host). */
|
|
6131
|
+
const CIRCUIT_WINDOW_SIZE = 20;
|
|
6132
|
+
/** Maximum age (ms) of an outcome before it is dropped from the window. */
|
|
6133
|
+
const CIRCUIT_WINDOW_TTL_MS = 30_000;
|
|
6134
|
+
/**
|
|
6135
|
+
* Failure ratio (0..1) at which the breaker trips when the window
|
|
6136
|
+
* is full (i.e. CIRCUIT_WINDOW_SIZE outcomes recorded).
|
|
6137
|
+
*
|
|
6138
|
+
* Tuned to be permissive enough that a few transient failures during
|
|
6139
|
+
* normal operation do not trip the breaker, but tight enough that a
|
|
6140
|
+
* persistent upstream outage (where >= half of recent calls are
|
|
6141
|
+
* failing) is detected quickly. With WINDOW=20 and threshold=0.6 we
|
|
6142
|
+
* need 12 failures within the most recent 20 attempts to trip.
|
|
6143
|
+
*/
|
|
6144
|
+
const CIRCUIT_TRIP_FAILURE_RATIO = 0.6;
|
|
6145
|
+
/**
|
|
6146
|
+
* Minimum number of recent outcomes required before the failure-ratio
|
|
6147
|
+
* gate is even evaluated. Without this guard, a single failure on a
|
|
6148
|
+
* cold host would give a 100 % ratio and trip the breaker on the
|
|
6149
|
+
* second attempt — which would be useless protection while also being
|
|
6150
|
+
* highly destructive.
|
|
6151
|
+
*/
|
|
6152
|
+
const CIRCUIT_MIN_SAMPLES = 8;
|
|
6153
|
+
/**
|
|
6154
|
+
* How long the breaker stays open before transitioning to half-open
|
|
6155
|
+
* (i.e. allowing the next request through as a probe). The next
|
|
6156
|
+
* outcome — success or failure — fully recloses or re-opens the
|
|
6157
|
+
* breaker. 5 s aligns with typical upstream recovery times and is
|
|
6158
|
+
* short enough that the next caller drives the probe.
|
|
6159
|
+
*/
|
|
6160
|
+
const CIRCUIT_OPEN_COOLDOWN_MS = 5_000;
|
|
6161
|
+
/**
|
|
6162
|
+
* Fail-fast latency. We sleep a short, deterministic amount before
|
|
6163
|
+
* rejecting so the upstream caller's outer timeout (commonly 10–25 s)
|
|
6164
|
+
* sees a clean rejection rather than a synchronous reject-storm that
|
|
6165
|
+
* could starve other event-loop work. 100 ms aligns with the user's
|
|
6166
|
+
* G1 analysis target: "cap per-symbol fallback latency at 100 ms
|
|
6167
|
+
* during outages — eliminating the 25 s × N-concurrent-symbols
|
|
6168
|
+
* event-loop hog."
|
|
6169
|
+
*/
|
|
6170
|
+
const CIRCUIT_FAIL_FAST_LATENCY_MS = 100;
|
|
6171
|
+
const circuitStates = new Map();
|
|
6172
|
+
function getCircuitState(host) {
|
|
6173
|
+
let state = circuitStates.get(host);
|
|
6174
|
+
if (!state) {
|
|
6175
|
+
state = {
|
|
6176
|
+
host,
|
|
6177
|
+
recent: [],
|
|
6178
|
+
openedAt: 0,
|
|
6179
|
+
lastTripFailureRatio: 0,
|
|
6180
|
+
};
|
|
6181
|
+
circuitStates.set(host, state);
|
|
6182
|
+
}
|
|
6183
|
+
return state;
|
|
6184
|
+
}
|
|
6185
|
+
function pruneStaleSamples(state, now) {
|
|
6186
|
+
const cutoff = now - CIRCUIT_WINDOW_TTL_MS;
|
|
6187
|
+
// Remove entries older than the TTL. Window is small so a simple
|
|
6188
|
+
// filter is fine — no need for a deque structure.
|
|
6189
|
+
if (state.recent.length === 0)
|
|
6190
|
+
return;
|
|
6191
|
+
let firstFreshIndex = 0;
|
|
6192
|
+
while (firstFreshIndex < state.recent.length &&
|
|
6193
|
+
state.recent[firstFreshIndex].atMs < cutoff) {
|
|
6194
|
+
firstFreshIndex += 1;
|
|
6195
|
+
}
|
|
6196
|
+
if (firstFreshIndex > 0) {
|
|
6197
|
+
state.recent.splice(0, firstFreshIndex);
|
|
6198
|
+
}
|
|
6199
|
+
}
|
|
6200
|
+
function recordOutcome(host, ok) {
|
|
6201
|
+
const now = Date.now();
|
|
6202
|
+
const state = getCircuitState(host);
|
|
6203
|
+
pruneStaleSamples(state, now);
|
|
6204
|
+
state.recent.push({ atMs: now, ok });
|
|
6205
|
+
if (state.recent.length > CIRCUIT_WINDOW_SIZE) {
|
|
6206
|
+
state.recent.shift();
|
|
6207
|
+
}
|
|
6208
|
+
// If we are in the open or half-open window and just received an
|
|
6209
|
+
// outcome, decide whether to close or re-open.
|
|
6210
|
+
if (state.openedAt > 0) {
|
|
6211
|
+
if (now - state.openedAt >= CIRCUIT_OPEN_COOLDOWN_MS) {
|
|
6212
|
+
// Half-open probe outcome arrived.
|
|
6213
|
+
if (ok) {
|
|
6214
|
+
// Recovered. Close the breaker.
|
|
6215
|
+
state.openedAt = 0;
|
|
6216
|
+
state.lastTripFailureRatio = 0;
|
|
6217
|
+
getLogger().info(`Circuit breaker for ${host} closed — upstream recovered`, { host });
|
|
6218
|
+
}
|
|
6219
|
+
else {
|
|
6220
|
+
// Probe failed. Re-open the breaker for another cooldown.
|
|
6221
|
+
state.openedAt = now;
|
|
6222
|
+
}
|
|
6223
|
+
return;
|
|
6224
|
+
}
|
|
6225
|
+
// Still inside the cooldown window — outcomes are recorded for
|
|
6226
|
+
// statistics but the breaker stays open regardless.
|
|
6227
|
+
return;
|
|
6228
|
+
}
|
|
6229
|
+
// Closed breaker — evaluate whether the failure ratio has tripped.
|
|
6230
|
+
if (state.recent.length < CIRCUIT_MIN_SAMPLES)
|
|
6231
|
+
return;
|
|
6232
|
+
const failures = state.recent.reduce((acc, entry) => acc + (entry.ok ? 0 : 1), 0);
|
|
6233
|
+
const ratio = failures / state.recent.length;
|
|
6234
|
+
if (ratio >= CIRCUIT_TRIP_FAILURE_RATIO) {
|
|
6235
|
+
state.openedAt = now;
|
|
6236
|
+
state.lastTripFailureRatio = ratio;
|
|
6237
|
+
getLogger().warn(`Circuit breaker for ${host} opened — ${failures}/${state.recent.length} recent attempts failed (${(ratio * 100).toFixed(0)}%); fail-fast for ${CIRCUIT_OPEN_COOLDOWN_MS}ms`, {
|
|
6238
|
+
host,
|
|
6239
|
+
recentFailures: failures,
|
|
6240
|
+
recentSamples: state.recent.length,
|
|
6241
|
+
failureRatio: ratio,
|
|
6242
|
+
cooldownMs: CIRCUIT_OPEN_COOLDOWN_MS,
|
|
6243
|
+
});
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
function isCircuitOpen(host, now) {
|
|
6247
|
+
const state = circuitStates.get(host);
|
|
6248
|
+
if (!state || state.openedAt === 0)
|
|
6249
|
+
return false;
|
|
6250
|
+
pruneStaleSamples(state, now);
|
|
6251
|
+
return now - state.openedAt < CIRCUIT_OPEN_COOLDOWN_MS;
|
|
6252
|
+
}
|
|
6253
|
+
/**
|
|
6254
|
+
* Error thrown by {@link fetchWithRetry} when the per-host circuit
|
|
6255
|
+
* breaker is open and fail-fast suppression is in effect.
|
|
6256
|
+
*
|
|
6257
|
+
* Carries the host, the failure ratio that tripped the breaker, and
|
|
6258
|
+
* the remaining cooldown so callers can render an actionable log.
|
|
6259
|
+
*/
|
|
6260
|
+
class CircuitOpenError extends Error {
|
|
6261
|
+
code = "MASSIVE_CIRCUIT_OPEN";
|
|
6262
|
+
host;
|
|
6263
|
+
tripFailureRatio;
|
|
6264
|
+
cooldownRemainingMs;
|
|
6265
|
+
constructor(host, tripFailureRatio, cooldownRemainingMs) {
|
|
6266
|
+
super(`Circuit open for ${host} — fail-fast (recent failure ratio ${(tripFailureRatio * 100).toFixed(0)}%, retry in ${cooldownRemainingMs}ms)`);
|
|
6267
|
+
this.name = "CircuitOpenError";
|
|
6268
|
+
this.host = host;
|
|
6269
|
+
this.tripFailureRatio = tripFailureRatio;
|
|
6270
|
+
this.cooldownRemainingMs = cooldownRemainingMs;
|
|
6271
|
+
}
|
|
6272
|
+
}
|
|
6273
|
+
/**
|
|
6274
|
+
* Force-close the breaker for a given host. Exposed for tests and
|
|
6275
|
+
* operator-runbook scripts so a stuck-open breaker can be reset
|
|
6276
|
+
* without bouncing the process. Not intended for hot-path use.
|
|
6277
|
+
*
|
|
6278
|
+
* @param host The hostname whose breaker should be reset.
|
|
6279
|
+
*/
|
|
6280
|
+
function resetCircuitBreaker(host) {
|
|
6281
|
+
const state = circuitStates.get(host);
|
|
6282
|
+
if (!state)
|
|
6283
|
+
return;
|
|
6284
|
+
state.openedAt = 0;
|
|
6285
|
+
state.lastTripFailureRatio = 0;
|
|
6286
|
+
state.recent = [];
|
|
6287
|
+
}
|
|
6288
|
+
/**
|
|
6289
|
+
* Snapshot of all known per-host circuit-breaker states. Intended for
|
|
6290
|
+
* an operational-truth / status endpoint to surface upstream health.
|
|
6291
|
+
*
|
|
6292
|
+
* @returns Map of host → {open, openedAt, recentSamples, failureRatio,
|
|
6293
|
+
* lastTripFailureRatio}.
|
|
6294
|
+
*/
|
|
6295
|
+
function getCircuitBreakerSnapshot() {
|
|
6296
|
+
const now = Date.now();
|
|
6297
|
+
const snapshot = {};
|
|
6298
|
+
for (const [host, state] of circuitStates) {
|
|
6299
|
+
pruneStaleSamples(state, now);
|
|
6300
|
+
const failures = state.recent.reduce((acc, entry) => acc + (entry.ok ? 0 : 1), 0);
|
|
6301
|
+
const ratio = state.recent.length > 0 ? failures / state.recent.length : 0;
|
|
6302
|
+
snapshot[host] = {
|
|
6303
|
+
open: state.openedAt > 0 && now - state.openedAt < CIRCUIT_OPEN_COOLDOWN_MS,
|
|
6304
|
+
openedAt: state.openedAt,
|
|
6305
|
+
cooldownRemainingMs: state.openedAt > 0
|
|
6306
|
+
? Math.max(0, CIRCUIT_OPEN_COOLDOWN_MS - (now - state.openedAt))
|
|
6307
|
+
: 0,
|
|
6308
|
+
recentSamples: state.recent.length,
|
|
6309
|
+
failureRatio: ratio,
|
|
6310
|
+
lastTripFailureRatio: state.lastTripFailureRatio,
|
|
6311
|
+
};
|
|
6312
|
+
}
|
|
6313
|
+
return snapshot;
|
|
6314
|
+
}
|
|
6315
|
+
function hostnameFromUrl(url) {
|
|
6316
|
+
try {
|
|
6317
|
+
return new URL(url).hostname;
|
|
6318
|
+
}
|
|
6319
|
+
catch {
|
|
6320
|
+
return null;
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
function sleep(ms) {
|
|
6324
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6325
|
+
}
|
|
6045
6326
|
/**
|
|
6046
6327
|
* Debug logging utility that respects environment debug flags.
|
|
6047
6328
|
* Logs messages through the configured structured logger when LUMIC_DEBUG
|
|
@@ -6153,9 +6434,46 @@ function hideApiKeyFromurl(url) {
|
|
|
6153
6434
|
* @throws Will throw an error if the fetch fails after the specified number of retries.
|
|
6154
6435
|
*/
|
|
6155
6436
|
async function fetchWithRetry(url, options = {}, retries = 3, initialBackoff = 1000) {
|
|
6437
|
+
// Per-host circuit-breaker check. When upstream is failing
|
|
6438
|
+
// pervasively (e.g. Massive REST in an outage), short-circuit to a
|
|
6439
|
+
// 100 ms fail-fast rejection so the caller's outer timeout (25 s,
|
|
6440
|
+
// 10 s, etc.) does not pile concurrent symbol fetches against a
|
|
6441
|
+
// dead host. Without this, an upstream outage during a screener tick
|
|
6442
|
+
// can lock up the event loop for up to (timeout × N-concurrent-symbols).
|
|
6443
|
+
const host = hostnameFromUrl(url);
|
|
6444
|
+
if (host) {
|
|
6445
|
+
const now = Date.now();
|
|
6446
|
+
if (isCircuitOpen(host, now)) {
|
|
6447
|
+
const state = getCircuitState(host);
|
|
6448
|
+
const cooldownRemainingMs = Math.max(0, CIRCUIT_OPEN_COOLDOWN_MS - (now - state.openedAt));
|
|
6449
|
+
await sleep(CIRCUIT_FAIL_FAST_LATENCY_MS);
|
|
6450
|
+
throw new CircuitOpenError(host, state.lastTripFailureRatio, cooldownRemainingMs);
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6156
6453
|
return withRetry(async () => {
|
|
6157
|
-
|
|
6454
|
+
let response;
|
|
6455
|
+
try {
|
|
6456
|
+
response = await fetch(url, options);
|
|
6457
|
+
}
|
|
6458
|
+
catch (networkError) {
|
|
6459
|
+
// Network failure (e.g. DNS, connection refused, TLS abort).
|
|
6460
|
+
// Record against the breaker so a sustained outage trips it
|
|
6461
|
+
// for subsequent calls; then re-throw so withRetry handles
|
|
6462
|
+
// its own retry policy.
|
|
6463
|
+
if (host)
|
|
6464
|
+
recordOutcome(host, false);
|
|
6465
|
+
throw networkError;
|
|
6466
|
+
}
|
|
6158
6467
|
if (!response.ok) {
|
|
6468
|
+
// Classify the outcome for the circuit breaker. 5xx and 429
|
|
6469
|
+
// are upstream-health signals (record as failure). 4xx client
|
|
6470
|
+
// errors are caller-side and should NOT affect the breaker —
|
|
6471
|
+
// a request with a bad API key shouldn't trip the host's
|
|
6472
|
+
// breaker for everyone else.
|
|
6473
|
+
const upstreamUnhealthy = response.status === 429 ||
|
|
6474
|
+
(response.status >= 500 && response.status < 600);
|
|
6475
|
+
if (host)
|
|
6476
|
+
recordOutcome(host, !upstreamUnhealthy);
|
|
6159
6477
|
// Enhanced HTTP error handling with specific error types
|
|
6160
6478
|
if (response.status === 429) {
|
|
6161
6479
|
// Check for Retry-After header
|
|
@@ -6187,6 +6505,10 @@ async function fetchWithRetry(url, options = {}, retries = 3, initialBackoff = 1
|
|
|
6187
6505
|
error.response = response;
|
|
6188
6506
|
throw error;
|
|
6189
6507
|
}
|
|
6508
|
+
// Success — record against the breaker so a healthy upstream
|
|
6509
|
+
// closes any half-open state cleanly.
|
|
6510
|
+
if (host)
|
|
6511
|
+
recordOutcome(host, true);
|
|
6190
6512
|
return response;
|
|
6191
6513
|
}, {
|
|
6192
6514
|
maxRetries: retries,
|
|
@@ -69377,6 +69699,17 @@ const adaptic = {
|
|
|
69377
69699
|
logIfDebug: logIfDebug,
|
|
69378
69700
|
fetchWithRetry: fetchWithRetry,
|
|
69379
69701
|
validateMassiveApiKey: validateMassiveApiKey,
|
|
69702
|
+
/**
|
|
69703
|
+
* Force-close a stuck-open per-host circuit breaker. Operator
|
|
69704
|
+
* runbook utility — see {@link misc.resetCircuitBreaker}.
|
|
69705
|
+
*/
|
|
69706
|
+
resetCircuitBreaker: resetCircuitBreaker,
|
|
69707
|
+
/**
|
|
69708
|
+
* Read-only snapshot of all per-host circuit-breaker states for
|
|
69709
|
+
* use in operational-truth endpoints. See
|
|
69710
|
+
* {@link misc.getCircuitBreakerSnapshot}.
|
|
69711
|
+
*/
|
|
69712
|
+
getCircuitBreakerSnapshot: getCircuitBreakerSnapshot,
|
|
69380
69713
|
},
|
|
69381
69714
|
rateLimiter: {
|
|
69382
69715
|
TokenBucketRateLimiter,
|
|
@@ -69385,5 +69718,5 @@ const adaptic = {
|
|
|
69385
69718
|
};
|
|
69386
69719
|
const adptc = adaptic;
|
|
69387
69720
|
|
|
69388
|
-
export { API_RETRY_CONFIGS, AVNewsArticleSchema, AVNewsResponseSchema, AdapticUtilsError, AlpacaAccountDetailsSchema, AlpacaApiError, AlpacaBarSchema, AlpacaClient, AlpacaCryptoBarsResponseSchema, AlpacaHistoricalBarsResponseSchema, AlpacaLatestBarsResponseSchema, AlpacaLatestQuotesResponseSchema, AlpacaLatestTradesResponseSchema, AlpacaMarketDataAPI, AlpacaNewsArticleSchema, AlpacaNewsResponseSchema, AlpacaOrderSchema, AlpacaOrdersArraySchema, AlpacaPortfolioHistoryResponseSchema, AlpacaPositionSchema, AlpacaPositionsArraySchema, AlpacaQuoteSchema, AlpacaTradeSchema, AlpacaTradingAPI, AlphaVantageError, AlphaVantageQuoteResponseSchema, AssetAllocationEngine, AuthenticationError, AutonomyMode, BTC_PAIRS, BarError, CryptoDataError, CryptoOrderError, DEFAULT_CACHE_OPTIONS, DEFAULT_RISK_FREE_RATE, DEFAULT_TIMEOUTS, DEFAULT_TRADING_POLICY, DataFormatError, DecisionMemoryOutcome, DecisionOutcome, DecisionRecordStatus, HttpClientError, HttpServerError, KEEP_ALIVE_DEFAULTS, LlmProvider, MARKET_DATA_API, MassiveAggregatesResponseSchema, MassiveApiError, MassiveDailyOpenCloseSchema, MassiveErrorResponseSchema, MassiveGroupedDailyResponseSchema, MassiveLastTradeResponseSchema, MassiveTickerDetailsResponseSchema, MassiveTickerInfoSchema, MassiveTradeSchema as MassiveTradeZodSchema, MassiveTradesResponseSchema, NetworkError, NewsError, OptionStrategyError, OptionsDataError, OverlaySeverity, OverlayStatus, OverlayType, QuoteError, RISK_FREE_RATE_TTL_MS, RateLimitError, RawMassivePriceDataSchema, StampedeProtectedCache, TRADING_API, TimeoutError, TokenBucketRateLimiter, TradeError, TrailingStopValidationError, USDC_PAIRS, USDT_PAIRS, USD_PAIRS, ValidationError, ValidationResponseError, WEBSOCKET_STREAMS, WebSocketError, account, adaptic, adptc, alpaca, analyzeBars, approximateImpliedVolatility, atrNs as atr, bracketOrders, buildOCCSymbol, buildOptionSymbol, buyCryptoNotional, buyToClose, buyToOpen, buyWithStopLoss, buyWithTrailingStop, calculateMoneyness, calculateOrderValue, calculatePeriodPerformance, calculatePutCallRatio, calculateTotalFilledValue, cancelAllCryptoOrders, cancelOCOOrder, cancelOTOOrder, cancelTrailingStop, cancelTrailingStopsForSymbol, checkTradingEligibility, clearClientCache, clock, closeAllOptionPositions, closeOptionPosition, createAlpacaClient, createAlpacaMarketDataAPI, createAlpacaTradingAPI, createBracketOrder, createButterflySpread, createClientFromEnv, createCoveredCall, createCryptoLimitOrder, createCryptoMarketOrder, createCryptoOrder, createCryptoStopLimitOrder, createCryptoStopOrder, createExecutorFromTradingAPI, createIronCondor$1 as createIronCondor, createIronCondor as createIronCondorAdvanced, createMultiLegOptionOrder, createOCOOrder, createOTOOrder, createOptionOrder, createPortfolioTrailingStops, createProtectiveBracket, createStampedeProtectedCache, createStraddle$1 as createStraddle, createStraddle as createStraddleAdvanced, createStrangle$1 as createStrangle, createStrangle as createStrangleAdvanced, createStreamManager, createTimeoutSignal, createTrailingStop, createVerticalSpread$1 as createVerticalSpread, createVerticalSpread as createVerticalSpreadAdvanced, entryWithPercentStopLoss, exerciseOption, extractGreeks, filterByExpiration, filterByStrike, filterByType, filterOrdersByDateRange, findATMOptions, findATMStrikes, findNearestExpiration, findOptionsByDelta, formatOrderForLog, formatOrderSummary, generateOptimalAllocation, getAccountConfiguration, getAccountDetails, getAccountSummary, getAgentPoolStatus, getAllOrders, getAlpacaCalendar, getAlpacaClock, getAverageDailyVolume, getBars, getBuyingPower, getCachedRiskFreeRateSync, getCachedRiskFreeRateSyncWithProvenance, getCrypto24HourChange, getCryptoBars, getCryptoDailyPrices, getCryptoPairsByQuote, getCryptoPrice, getCryptoSnapshots, getCryptoSpread, getCryptoStreamUrl, getCryptoTrades, getCurrentPrice, getCurrentPrices, getDailyPrices, getDailyReturns, getDaysToExpiration, getDefaultRiskProfile, getEquityCurve, getExpirationDates, getFilledOrders, getGroupedOptionChain, getHistoricalOptionsBars, getHistoricalTrades, getIntradayPrices, getLatestBars, getLatestCryptoQuotes, getLatestCryptoTrades, getLatestNews, getLatestOptionsQuotes, getLatestOptionsTrades, getLatestQuote, getLatestQuotes, getLatestTrade, getLatestTrades, getLogger, getMarginInfo, getNews, getNewsForSymbols, getOCOOrderStatus, getOTOOrderStatus, getOpenCryptoOrders, getOpenOrders$1 as getOpenOrdersQuery, getOpenTrailingStops, getOptionChain, getOptionContract, getOptionContracts, getOptionSpread, getOptionsChain, getOptionsSnapshots, getOptionsStreamUrl, getOptionsTradingLevel, getOrderHistory, getOrdersBySymbol, getPDTStatus, getPopularCryptoPairs, getPortfolioHistory, getPreviousClose, getPriceRange, getRiskFreeRate, getRiskFreeRateWithProvenance, getSpread, getSpreads, getStockStreamUrl, getStrikePrices, getSupportedCryptoPairs, getSymbolSentiment, getTimeout, getTradeVolume, getTradingApiUrl, getTradingWebSocketUrl, getTrailingStopHWM, groupOrdersByStatus, groupOrdersBySymbol, hasActiveTrailingStop, hasGoodLiquidity as hasOptionLiquidity, hasGoodLiquidity$1 as hasStockLiquidity, hasSufficientVolume, httpAgent, httpsAgent, isContractTradable, isCryptoPair, isExpiringWithin, isMarginAccount, isOptionOrderCancelable, isOptionOrderTerminal, isOrderFillable, isOrderFilled, isOrderOpen, isOrderTerminal$1 as isOrderTerminalStatus, isSupportedCryptoPair, isTransientNetworkError, index$1 as legacyApi, limitBuyWithTakeProfit, ocoOrders, orderUtils, otoOrders, paginate, paginateAll, parseOCCSymbol, protectLongPosition, protectShortPosition, rateLimiters, resetLogger, resetRiskFreeRateCache, riskNs as risk, rollOptionPosition, roundPriceForAlpaca$3 as roundPriceForAlpaca, roundPriceForAlpacaNumber, safeValidateResponse, searchNews, sellAllCrypto, sellCryptoNotional, sellToClose, sellToOpen, setLogger, setRiskFreeRate, shortWithStopLoss, sortOrdersByDate, strategyNs as strategy, index as tradingPolicy, trailingStops, updateAccountConfiguration, updateTrailingStop, validateAlpacaCredentials, validateAlphaVantageApiKey, validateMassiveApiKey$1 as validateMassiveApiKey, validateMultiLegOrder, validateResponse, verifyFetchKeepAlive, volatilityNs as volatility, waitForOrderFill, withRetry, withTimeout };
|
|
69721
|
+
export { API_RETRY_CONFIGS, AVNewsArticleSchema, AVNewsResponseSchema, AdapticUtilsError, AlpacaAccountDetailsSchema, AlpacaApiError, AlpacaBarSchema, AlpacaClient, AlpacaCryptoBarsResponseSchema, AlpacaHistoricalBarsResponseSchema, AlpacaLatestBarsResponseSchema, AlpacaLatestQuotesResponseSchema, AlpacaLatestTradesResponseSchema, AlpacaMarketDataAPI, AlpacaNewsArticleSchema, AlpacaNewsResponseSchema, AlpacaOrderSchema, AlpacaOrdersArraySchema, AlpacaPortfolioHistoryResponseSchema, AlpacaPositionSchema, AlpacaPositionsArraySchema, AlpacaQuoteSchema, AlpacaTradeSchema, AlpacaTradingAPI, AlphaVantageError, AlphaVantageQuoteResponseSchema, AssetAllocationEngine, AuthenticationError, AutonomyMode, BTC_PAIRS, BarError, CircuitOpenError, CryptoDataError, CryptoOrderError, DEFAULT_CACHE_OPTIONS, DEFAULT_RISK_FREE_RATE, DEFAULT_TIMEOUTS, DEFAULT_TRADING_POLICY, DataFormatError, DecisionMemoryOutcome, DecisionOutcome, DecisionRecordStatus, HttpClientError, HttpServerError, KEEP_ALIVE_DEFAULTS, LlmProvider, MARKET_DATA_API, MassiveAggregatesResponseSchema, MassiveApiError, MassiveDailyOpenCloseSchema, MassiveErrorResponseSchema, MassiveGroupedDailyResponseSchema, MassiveLastTradeResponseSchema, MassiveTickerDetailsResponseSchema, MassiveTickerInfoSchema, MassiveTradeSchema as MassiveTradeZodSchema, MassiveTradesResponseSchema, NetworkError, NewsError, OptionStrategyError, OptionsDataError, OverlaySeverity, OverlayStatus, OverlayType, QuoteError, RISK_FREE_RATE_TTL_MS, RateLimitError, RawMassivePriceDataSchema, StampedeProtectedCache, TRADING_API, TimeoutError, TokenBucketRateLimiter, TradeError, TrailingStopValidationError, USDC_PAIRS, USDT_PAIRS, USD_PAIRS, ValidationError, ValidationResponseError, WEBSOCKET_STREAMS, WebSocketError, account, adaptic, adptc, alpaca, analyzeBars, approximateImpliedVolatility, atrNs as atr, bracketOrders, buildOCCSymbol, buildOptionSymbol, buyCryptoNotional, buyToClose, buyToOpen, buyWithStopLoss, buyWithTrailingStop, calculateMoneyness, calculateOrderValue, calculatePeriodPerformance, calculatePutCallRatio, calculateTotalFilledValue, cancelAllCryptoOrders, cancelOCOOrder, cancelOTOOrder, cancelTrailingStop, cancelTrailingStopsForSymbol, checkTradingEligibility, clearClientCache, clock, closeAllOptionPositions, closeOptionPosition, createAlpacaClient, createAlpacaMarketDataAPI, createAlpacaTradingAPI, createBracketOrder, createButterflySpread, createClientFromEnv, createCoveredCall, createCryptoLimitOrder, createCryptoMarketOrder, createCryptoOrder, createCryptoStopLimitOrder, createCryptoStopOrder, createExecutorFromTradingAPI, createIronCondor$1 as createIronCondor, createIronCondor as createIronCondorAdvanced, createMultiLegOptionOrder, createOCOOrder, createOTOOrder, createOptionOrder, createPortfolioTrailingStops, createProtectiveBracket, createStampedeProtectedCache, createStraddle$1 as createStraddle, createStraddle as createStraddleAdvanced, createStrangle$1 as createStrangle, createStrangle as createStrangleAdvanced, createStreamManager, createTimeoutSignal, createTrailingStop, createVerticalSpread$1 as createVerticalSpread, createVerticalSpread as createVerticalSpreadAdvanced, entryWithPercentStopLoss, exerciseOption, extractGreeks, filterByExpiration, filterByStrike, filterByType, filterOrdersByDateRange, findATMOptions, findATMStrikes, findNearestExpiration, findOptionsByDelta, formatOrderForLog, formatOrderSummary, generateOptimalAllocation, getAccountConfiguration, getAccountDetails, getAccountSummary, getAgentPoolStatus, getAllOrders, getAlpacaCalendar, getAlpacaClock, getAverageDailyVolume, getBars, getBuyingPower, getCachedRiskFreeRateSync, getCachedRiskFreeRateSyncWithProvenance, getCrypto24HourChange, getCryptoBars, getCryptoDailyPrices, getCryptoPairsByQuote, getCryptoPrice, getCryptoSnapshots, getCryptoSpread, getCryptoStreamUrl, getCryptoTrades, getCurrentPrice, getCurrentPrices, getDailyPrices, getDailyReturns, getDaysToExpiration, getDefaultRiskProfile, getEquityCurve, getExpirationDates, getFilledOrders, getGroupedOptionChain, getHistoricalOptionsBars, getHistoricalTrades, getIntradayPrices, getLatestBars, getLatestCryptoQuotes, getLatestCryptoTrades, getLatestNews, getLatestOptionsQuotes, getLatestOptionsTrades, getLatestQuote, getLatestQuotes, getLatestTrade, getLatestTrades, getLogger, getMarginInfo, getNews, getNewsForSymbols, getOCOOrderStatus, getOTOOrderStatus, getOpenCryptoOrders, getOpenOrders$1 as getOpenOrdersQuery, getOpenTrailingStops, getOptionChain, getOptionContract, getOptionContracts, getOptionSpread, getOptionsChain, getOptionsSnapshots, getOptionsStreamUrl, getOptionsTradingLevel, getOrderHistory, getOrdersBySymbol, getPDTStatus, getPopularCryptoPairs, getPortfolioHistory, getPreviousClose, getPriceRange, getRiskFreeRate, getRiskFreeRateWithProvenance, getSpread, getSpreads, getStockStreamUrl, getStrikePrices, getSupportedCryptoPairs, getSymbolSentiment, getTimeout, getTradeVolume, getTradingApiUrl, getTradingWebSocketUrl, getTrailingStopHWM, groupOrdersByStatus, groupOrdersBySymbol, hasActiveTrailingStop, hasGoodLiquidity as hasOptionLiquidity, hasGoodLiquidity$1 as hasStockLiquidity, hasSufficientVolume, httpAgent, httpsAgent, isContractTradable, isCryptoPair, isExpiringWithin, isMarginAccount, isOptionOrderCancelable, isOptionOrderTerminal, isOrderFillable, isOrderFilled, isOrderOpen, isOrderTerminal$1 as isOrderTerminalStatus, isSupportedCryptoPair, isTransientNetworkError, index$1 as legacyApi, limitBuyWithTakeProfit, ocoOrders, orderUtils, otoOrders, paginate, paginateAll, parseOCCSymbol, protectLongPosition, protectShortPosition, rateLimiters, resetLogger, resetRiskFreeRateCache, riskNs as risk, rollOptionPosition, roundPriceForAlpaca$3 as roundPriceForAlpaca, roundPriceForAlpacaNumber, safeValidateResponse, searchNews, sellAllCrypto, sellCryptoNotional, sellToClose, sellToOpen, setLogger, setRiskFreeRate, shortWithStopLoss, sortOrdersByDate, strategyNs as strategy, index as tradingPolicy, trailingStops, updateAccountConfiguration, updateTrailingStop, validateAlpacaCredentials, validateAlphaVantageApiKey, validateMassiveApiKey$1 as validateMassiveApiKey, validateMultiLegOrder, validateResponse, verifyFetchKeepAlive, volatilityNs as volatility, waitForOrderFill, withRetry, withTimeout };
|
|
69389
69722
|
//# sourceMappingURL=index.mjs.map
|