@adaptic/utils 0.0.1005 → 0.0.1006
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 +548 -491
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +548 -491
- package/dist/index.mjs.map +1 -1
- package/dist/test.js +155 -5
- package/dist/test.js.map +1 -1
- package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -4
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2175,6 +2175,419 @@ function validateAlphaVantageApiKey(apiKey) {
|
|
|
2175
2175
|
}
|
|
2176
2176
|
}
|
|
2177
2177
|
|
|
2178
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
2179
|
+
maxRetries: 3,
|
|
2180
|
+
baseDelayMs: 1000,
|
|
2181
|
+
maxDelayMs: 30000,
|
|
2182
|
+
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
2183
|
+
retryOnNetworkError: true,
|
|
2184
|
+
};
|
|
2185
|
+
/**
|
|
2186
|
+
* Node.js / undici / system error codes that represent transient network
|
|
2187
|
+
* conditions. Present on `error.code` for net/http/dns/undici errors.
|
|
2188
|
+
*/
|
|
2189
|
+
const RETRYABLE_ERROR_CODES = new Set([
|
|
2190
|
+
"ETIMEDOUT",
|
|
2191
|
+
"ESOCKETTIMEDOUT",
|
|
2192
|
+
"ECONNRESET",
|
|
2193
|
+
"ECONNREFUSED",
|
|
2194
|
+
"EHOSTUNREACH",
|
|
2195
|
+
"ENETUNREACH",
|
|
2196
|
+
"EAI_AGAIN",
|
|
2197
|
+
"EPIPE",
|
|
2198
|
+
"ECONNABORTED",
|
|
2199
|
+
"ENOTFOUND",
|
|
2200
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
2201
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
2202
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
2203
|
+
"UND_ERR_SOCKET",
|
|
2204
|
+
"UND_ERR_CLOSED",
|
|
2205
|
+
"UND_ERR_REQ_CONTENT_LENGTH_MISMATCH",
|
|
2206
|
+
]);
|
|
2207
|
+
/**
|
|
2208
|
+
* Error constructor names / `error.name` values that indicate transient
|
|
2209
|
+
* abort / timeout conditions.
|
|
2210
|
+
*/
|
|
2211
|
+
const RETRYABLE_ERROR_NAMES = new Set([
|
|
2212
|
+
"AbortError",
|
|
2213
|
+
"TimeoutError",
|
|
2214
|
+
"FetchError",
|
|
2215
|
+
"RequestTimeoutError",
|
|
2216
|
+
"ConnectTimeoutError",
|
|
2217
|
+
"HeadersTimeoutError",
|
|
2218
|
+
"BodyTimeoutError",
|
|
2219
|
+
]);
|
|
2220
|
+
/**
|
|
2221
|
+
* Message-pattern fallback for libraries that discard error codes/names but
|
|
2222
|
+
* preserve text (e.g., some Apollo/axios wrappers).
|
|
2223
|
+
*/
|
|
2224
|
+
const RETRYABLE_MESSAGE_PATTERNS = [
|
|
2225
|
+
/aborted/i,
|
|
2226
|
+
/timeout/i,
|
|
2227
|
+
/timed out/i,
|
|
2228
|
+
/network error/i,
|
|
2229
|
+
/socket hang up/i,
|
|
2230
|
+
/connection (reset|refused|closed)/i,
|
|
2231
|
+
/ECONNRESET/,
|
|
2232
|
+
/ETIMEDOUT/,
|
|
2233
|
+
/ECONNREFUSED/,
|
|
2234
|
+
/EAI_AGAIN/,
|
|
2235
|
+
/UND_ERR_/,
|
|
2236
|
+
];
|
|
2237
|
+
/**
|
|
2238
|
+
* Walks the `error.cause` chain (capped to avoid cycles) and tests whether
|
|
2239
|
+
* any link along the chain looks like a transient network error. Modern APIs
|
|
2240
|
+
* (undici, fetch, Apollo Client 3.8+) wrap the root network failure as a
|
|
2241
|
+
* `.cause`, so the surface `Error` may report a generic message while the
|
|
2242
|
+
* actionable signal lives one or more levels deeper.
|
|
2243
|
+
*
|
|
2244
|
+
* Exported for use by downstream consumers (engine services, per-call catch
|
|
2245
|
+
* blocks, application-level loggers) that need to demote recoverable
|
|
2246
|
+
* transient errors from ERROR to WARN. Aligns the whole stack on a single
|
|
2247
|
+
* canonical classifier so MassiveAPI, AlpacaAPI, and application-layer
|
|
2248
|
+
* retry handlers all treat the same network blips identically.
|
|
2249
|
+
*/
|
|
2250
|
+
function isTransientNetworkError(error) {
|
|
2251
|
+
const MAX_CAUSE_DEPTH = 6;
|
|
2252
|
+
let current = error;
|
|
2253
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && current; depth++) {
|
|
2254
|
+
if (current instanceof Error || typeof current === "object") {
|
|
2255
|
+
const err = current;
|
|
2256
|
+
if (typeof err.name === "string" && RETRYABLE_ERROR_NAMES.has(err.name)) {
|
|
2257
|
+
return true;
|
|
2258
|
+
}
|
|
2259
|
+
if (typeof err.code === "string" && RETRYABLE_ERROR_CODES.has(err.code)) {
|
|
2260
|
+
return true;
|
|
2261
|
+
}
|
|
2262
|
+
if (typeof err.message === "string") {
|
|
2263
|
+
for (const pattern of RETRYABLE_MESSAGE_PATTERNS) {
|
|
2264
|
+
if (pattern.test(err.message)) {
|
|
2265
|
+
return true;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
current = err.cause;
|
|
2270
|
+
}
|
|
2271
|
+
else {
|
|
2272
|
+
break;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
return false;
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Analyzes an error and determines if it's retryable.
|
|
2279
|
+
* @param error - The error to analyze
|
|
2280
|
+
* @param response - Optional Response object for HTTP errors
|
|
2281
|
+
* @param config - Retry configuration
|
|
2282
|
+
* @returns Structured error details
|
|
2283
|
+
*/
|
|
2284
|
+
function analyzeError(error, response, config) {
|
|
2285
|
+
// Handle Response objects with error status codes
|
|
2286
|
+
if (response && !response.ok) {
|
|
2287
|
+
const status = response.status;
|
|
2288
|
+
// Rate limit errors - always retryable
|
|
2289
|
+
if (status === 429) {
|
|
2290
|
+
const retryAfterHeader = response.headers.get("Retry-After");
|
|
2291
|
+
const retryAfter = retryAfterHeader
|
|
2292
|
+
? parseInt(retryAfterHeader, 10) * 1000
|
|
2293
|
+
: undefined;
|
|
2294
|
+
return {
|
|
2295
|
+
type: "RATE_LIMIT",
|
|
2296
|
+
reason: "Rate limit exceeded",
|
|
2297
|
+
status,
|
|
2298
|
+
retryAfter,
|
|
2299
|
+
isRetryable: true,
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
// Authentication errors - never retry
|
|
2303
|
+
if (status === 401 || status === 403) {
|
|
2304
|
+
return {
|
|
2305
|
+
type: "AUTH_ERROR",
|
|
2306
|
+
reason: status === 401
|
|
2307
|
+
? "Authentication failed - invalid credentials"
|
|
2308
|
+
: "Access forbidden - insufficient permissions",
|
|
2309
|
+
status,
|
|
2310
|
+
isRetryable: false,
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
// Server errors - check if in retryable list
|
|
2314
|
+
if (status >= 500 && status < 600) {
|
|
2315
|
+
return {
|
|
2316
|
+
type: "SERVER_ERROR",
|
|
2317
|
+
reason: `Server error (${status})`,
|
|
2318
|
+
status,
|
|
2319
|
+
isRetryable: config.retryableStatusCodes.includes(status),
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
// Other client errors - never retry
|
|
2323
|
+
if (status >= 400 && status < 500) {
|
|
2324
|
+
return {
|
|
2325
|
+
type: "CLIENT_ERROR",
|
|
2326
|
+
reason: `Client error (${status})`,
|
|
2327
|
+
status,
|
|
2328
|
+
isRetryable: false,
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
// Handle network errors (TypeError from fetch API)
|
|
2333
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
2334
|
+
return {
|
|
2335
|
+
type: "NETWORK_ERROR",
|
|
2336
|
+
reason: "Network connectivity issue",
|
|
2337
|
+
status: null,
|
|
2338
|
+
isRetryable: config.retryOnNetworkError,
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
// Handle transient network conditions: AbortError, TimeoutError,
|
|
2342
|
+
// Node/undici error codes (ETIMEDOUT, ECONNRESET, UND_ERR_*), and
|
|
2343
|
+
// wrapped failures exposed via error.cause. This catches the broad class
|
|
2344
|
+
// of infrastructure flakes that the TypeError-only check above misses.
|
|
2345
|
+
if (isTransientNetworkError(error)) {
|
|
2346
|
+
const reason = error instanceof Error ? error.message : "Transient network error";
|
|
2347
|
+
return {
|
|
2348
|
+
type: "NETWORK_ERROR",
|
|
2349
|
+
reason,
|
|
2350
|
+
status: null,
|
|
2351
|
+
isRetryable: config.retryOnNetworkError,
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2354
|
+
// Handle error objects with messages
|
|
2355
|
+
if (error instanceof Error) {
|
|
2356
|
+
// Parse error messages that might contain status information
|
|
2357
|
+
if (error.message.includes("429") || error.message.includes("RATE_LIMIT")) {
|
|
2358
|
+
const match = error.message.match(/RATE_LIMIT: 429:(\d+)/);
|
|
2359
|
+
const retryAfter = match ? parseInt(match[1], 10) : undefined;
|
|
2360
|
+
return {
|
|
2361
|
+
type: "RATE_LIMIT",
|
|
2362
|
+
reason: "Rate limit exceeded",
|
|
2363
|
+
status: 429,
|
|
2364
|
+
retryAfter,
|
|
2365
|
+
isRetryable: true,
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
if (error.message.includes("401") ||
|
|
2369
|
+
error.message.includes("403") ||
|
|
2370
|
+
error.message.includes("AUTH_ERROR")) {
|
|
2371
|
+
const status = error.message.includes("401") ? 401 : 403;
|
|
2372
|
+
return {
|
|
2373
|
+
type: "AUTH_ERROR",
|
|
2374
|
+
reason: `Authentication error (${status})`,
|
|
2375
|
+
status,
|
|
2376
|
+
isRetryable: false,
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
if (error.message.includes("SERVER_ERROR") ||
|
|
2380
|
+
error.message.match(/50[0-9]/)) {
|
|
2381
|
+
const statusMatch = error.message.match(/50[0-9]/);
|
|
2382
|
+
const status = statusMatch ? parseInt(statusMatch[0], 10) : 500;
|
|
2383
|
+
return {
|
|
2384
|
+
type: "SERVER_ERROR",
|
|
2385
|
+
reason: `Server error (${status})`,
|
|
2386
|
+
status,
|
|
2387
|
+
isRetryable: config.retryableStatusCodes.includes(status),
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
if (error.message.includes("network") ||
|
|
2391
|
+
error.message.includes("NETWORK_ERROR")) {
|
|
2392
|
+
return {
|
|
2393
|
+
type: "NETWORK_ERROR",
|
|
2394
|
+
reason: error.message,
|
|
2395
|
+
status: null,
|
|
2396
|
+
isRetryable: config.retryOnNetworkError,
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
// Unknown error - not retryable by default for safety
|
|
2401
|
+
return {
|
|
2402
|
+
type: "UNKNOWN",
|
|
2403
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
2404
|
+
status: null,
|
|
2405
|
+
isRetryable: false,
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Calculates the delay before the next retry attempt using exponential backoff with jitter.
|
|
2410
|
+
* @param attempt - Current attempt number (1-indexed)
|
|
2411
|
+
* @param baseDelay - Base delay in milliseconds
|
|
2412
|
+
* @param maxDelay - Maximum delay in milliseconds
|
|
2413
|
+
* @returns Delay in milliseconds
|
|
2414
|
+
*/
|
|
2415
|
+
function calculateBackoff(attempt, baseDelay, maxDelay) {
|
|
2416
|
+
// Exponential backoff: baseDelay * 2^(attempt-1)
|
|
2417
|
+
const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
|
|
2418
|
+
// Cap at maxDelay
|
|
2419
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
2420
|
+
// Add jitter (random value between 0% and 25% of the delay)
|
|
2421
|
+
const jitter = Math.random() * cappedDelay * 0.25;
|
|
2422
|
+
return Math.floor(cappedDelay + jitter);
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Wraps an async function with retry logic and exponential backoff.
|
|
2426
|
+
*
|
|
2427
|
+
* This utility handles transient errors in external API calls by automatically retrying
|
|
2428
|
+
* failed requests with intelligent backoff strategies. It respects rate limit headers,
|
|
2429
|
+
* fails fast on non-retryable errors, and provides detailed logging.
|
|
2430
|
+
*
|
|
2431
|
+
* @template T - The return type of the wrapped function
|
|
2432
|
+
* @param fn - The async function to wrap with retry logic
|
|
2433
|
+
* @param config - Retry configuration (merged with defaults)
|
|
2434
|
+
* @param label - A descriptive label for logging (e.g., 'Massive.fetchTickerInfo')
|
|
2435
|
+
* @returns A promise that resolves to the function's return value
|
|
2436
|
+
* @throws The last error encountered if all retries are exhausted
|
|
2437
|
+
*
|
|
2438
|
+
* @example
|
|
2439
|
+
* ```typescript
|
|
2440
|
+
* // Basic usage with defaults
|
|
2441
|
+
* const data = await withRetry(
|
|
2442
|
+
* async () => fetch('https://api.example.com/data'),
|
|
2443
|
+
* {},
|
|
2444
|
+
* 'ExampleAPI.fetchData'
|
|
2445
|
+
* );
|
|
2446
|
+
*
|
|
2447
|
+
* // Custom configuration for rate-limited API
|
|
2448
|
+
* const result = await withRetry(
|
|
2449
|
+
* async () => alphaVantageAPI.getQuote(symbol),
|
|
2450
|
+
* {
|
|
2451
|
+
* maxRetries: 5,
|
|
2452
|
+
* baseDelayMs: 5000,
|
|
2453
|
+
* maxDelayMs: 60000,
|
|
2454
|
+
* onRetry: (attempt, error) => {
|
|
2455
|
+
* getLogger().info(`Retry ${attempt} after error:`, error);
|
|
2456
|
+
* }
|
|
2457
|
+
* },
|
|
2458
|
+
* 'AlphaVantage.getQuote'
|
|
2459
|
+
* );
|
|
2460
|
+
* ```
|
|
2461
|
+
*/
|
|
2462
|
+
async function withRetry(fn, config = {}, label = "unknown") {
|
|
2463
|
+
const fullConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
2464
|
+
let lastError;
|
|
2465
|
+
for (let attempt = 1; attempt <= fullConfig.maxRetries; attempt++) {
|
|
2466
|
+
try {
|
|
2467
|
+
const result = await fn();
|
|
2468
|
+
// If we succeeded after retries, log it
|
|
2469
|
+
if (attempt > 1) {
|
|
2470
|
+
getLogger().info(`[${label}] Succeeded on attempt ${attempt}/${fullConfig.maxRetries}`);
|
|
2471
|
+
}
|
|
2472
|
+
return result;
|
|
2473
|
+
}
|
|
2474
|
+
catch (error) {
|
|
2475
|
+
lastError = error;
|
|
2476
|
+
// If this is the last attempt, throw the error.
|
|
2477
|
+
// Transient network classes (undici/fetch timeouts, ECONNRESET,
|
|
2478
|
+
// AbortError, etc.) are self-healing at the upstream retry layer —
|
|
2479
|
+
// the caller re-invokes on the next refresh/poll tick. Logging them
|
|
2480
|
+
// at ERROR produces alert noise that does not represent actionable
|
|
2481
|
+
// failures. Demote the transient class to WARN with a recovery hint;
|
|
2482
|
+
// reserve ERROR for non-transient final failures (auth, schema,
|
|
2483
|
+
// contract violations, unknown classes).
|
|
2484
|
+
if (attempt === fullConfig.maxRetries) {
|
|
2485
|
+
const isTransient = isTransientNetworkError(error);
|
|
2486
|
+
const logMeta = {
|
|
2487
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2488
|
+
attempts: fullConfig.maxRetries,
|
|
2489
|
+
timestamp: new Date().toISOString(),
|
|
2490
|
+
...(isTransient
|
|
2491
|
+
? {
|
|
2492
|
+
transient: true,
|
|
2493
|
+
recoveryHint: "Upstream caller should retry on next cycle",
|
|
2494
|
+
}
|
|
2495
|
+
: {}),
|
|
2496
|
+
};
|
|
2497
|
+
if (isTransient) {
|
|
2498
|
+
getLogger().warn(`[${label}] Failed after ${fullConfig.maxRetries} attempts (transient)`, logMeta);
|
|
2499
|
+
}
|
|
2500
|
+
else {
|
|
2501
|
+
getLogger().error(`[${label}] Failed after ${fullConfig.maxRetries} attempts`, logMeta);
|
|
2502
|
+
}
|
|
2503
|
+
throw error;
|
|
2504
|
+
}
|
|
2505
|
+
// Analyze the error to determine if we should retry
|
|
2506
|
+
const response = error instanceof Response ? error : null;
|
|
2507
|
+
const errorDetails = analyzeError(error, response, fullConfig);
|
|
2508
|
+
// If error is not retryable, fail immediately
|
|
2509
|
+
if (!errorDetails.isRetryable) {
|
|
2510
|
+
getLogger().error(`[${label}] Non-retryable error (${errorDetails.type})`, {
|
|
2511
|
+
reason: errorDetails.reason,
|
|
2512
|
+
status: errorDetails.status,
|
|
2513
|
+
timestamp: new Date().toISOString(),
|
|
2514
|
+
});
|
|
2515
|
+
throw error;
|
|
2516
|
+
}
|
|
2517
|
+
// Calculate delay for next retry
|
|
2518
|
+
let delayMs;
|
|
2519
|
+
if (errorDetails.type === "RATE_LIMIT" && errorDetails.retryAfter) {
|
|
2520
|
+
// Use Retry-After header if available
|
|
2521
|
+
delayMs = errorDetails.retryAfter;
|
|
2522
|
+
}
|
|
2523
|
+
else if (errorDetails.type === "RATE_LIMIT") {
|
|
2524
|
+
// For rate limits without Retry-After, use a longer minimum delay
|
|
2525
|
+
delayMs = Math.max(calculateBackoff(attempt, fullConfig.baseDelayMs, fullConfig.maxDelayMs), 5000);
|
|
2526
|
+
}
|
|
2527
|
+
else {
|
|
2528
|
+
// Standard exponential backoff with jitter
|
|
2529
|
+
delayMs = calculateBackoff(attempt, fullConfig.baseDelayMs, fullConfig.maxDelayMs);
|
|
2530
|
+
}
|
|
2531
|
+
// Log the retry attempt
|
|
2532
|
+
getLogger().warn(`[${label}] Attempt ${attempt}/${fullConfig.maxRetries} failed: ${errorDetails.reason}. Retrying in ${delayMs}ms...`, {
|
|
2533
|
+
attemptNumber: attempt,
|
|
2534
|
+
totalRetries: fullConfig.maxRetries,
|
|
2535
|
+
errorType: errorDetails.type,
|
|
2536
|
+
httpStatus: errorDetails.status,
|
|
2537
|
+
retryDelay: delayMs,
|
|
2538
|
+
timestamp: new Date().toISOString(),
|
|
2539
|
+
});
|
|
2540
|
+
// Call the optional retry callback
|
|
2541
|
+
if (fullConfig.onRetry) {
|
|
2542
|
+
fullConfig.onRetry(attempt, error);
|
|
2543
|
+
}
|
|
2544
|
+
// Wait before retrying
|
|
2545
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
// This should never be reached due to the throw in the last attempt,
|
|
2549
|
+
// but TypeScript needs this to satisfy the return type
|
|
2550
|
+
throw lastError;
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2553
|
+
* API-specific retry configurations for different external services.
|
|
2554
|
+
* These configurations are tuned based on each API's rate limits and characteristics.
|
|
2555
|
+
*/
|
|
2556
|
+
const API_RETRY_CONFIGS = {
|
|
2557
|
+
/** Massive.com API - 5 requests/second rate limit */
|
|
2558
|
+
MASSIVE: {
|
|
2559
|
+
maxRetries: 3,
|
|
2560
|
+
baseDelayMs: 1000,
|
|
2561
|
+
maxDelayMs: 30000,
|
|
2562
|
+
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
2563
|
+
retryOnNetworkError: true,
|
|
2564
|
+
},
|
|
2565
|
+
/** Alpha Vantage API - 5 requests/minute rate limit (more strict) */
|
|
2566
|
+
ALPHA_VANTAGE: {
|
|
2567
|
+
maxRetries: 5,
|
|
2568
|
+
baseDelayMs: 5000,
|
|
2569
|
+
maxDelayMs: 60000,
|
|
2570
|
+
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
2571
|
+
retryOnNetworkError: true,
|
|
2572
|
+
},
|
|
2573
|
+
/** Alpaca API - generally reliable, shorter retry window */
|
|
2574
|
+
ALPACA: {
|
|
2575
|
+
maxRetries: 3,
|
|
2576
|
+
baseDelayMs: 1000,
|
|
2577
|
+
maxDelayMs: 30000,
|
|
2578
|
+
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
2579
|
+
retryOnNetworkError: true,
|
|
2580
|
+
},
|
|
2581
|
+
/** Generic crypto API configuration */
|
|
2582
|
+
CRYPTO: {
|
|
2583
|
+
maxRetries: 3,
|
|
2584
|
+
baseDelayMs: 1000,
|
|
2585
|
+
maxDelayMs: 30000,
|
|
2586
|
+
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
2587
|
+
retryOnNetworkError: true,
|
|
2588
|
+
},
|
|
2589
|
+
};
|
|
2590
|
+
|
|
2178
2591
|
/**
|
|
2179
2592
|
* Structured error type hierarchy for all API integrations
|
|
2180
2593
|
*
|
|
@@ -2719,6 +3132,32 @@ const rateLimiters = {
|
|
|
2719
3132
|
}),
|
|
2720
3133
|
};
|
|
2721
3134
|
|
|
3135
|
+
/**
|
|
3136
|
+
* Bounded retry for TRANSIENT network faults on Alpaca market-data reads.
|
|
3137
|
+
*
|
|
3138
|
+
* 2026-07-30: the engine logged 637 `TypeError: fetch failed` / `read
|
|
3139
|
+
* ECONNRESET` errors against `/v2/stocks/bars` in a single session (~4 per
|
|
3140
|
+
* minute). Every OTHER Alpaca/Massive client in this package already routes
|
|
3141
|
+
* transient faults through {@link isTransientNetworkError} — this client was
|
|
3142
|
+
* the one that never adopted it, so a single reset socket killed the whole
|
|
3143
|
+
* read. Those failures surface in the engine's market-tape prompt context and
|
|
3144
|
+
* its intraday direction/path resolvers, so the decision model judged with a
|
|
3145
|
+
* blind tape: raw confidence collapsed from a 0.677 median to a degenerate
|
|
3146
|
+
* ~0.504 band, the entire distribution fell below the 0.55 admission floor,
|
|
3147
|
+
* and live participation went to near zero.
|
|
3148
|
+
*
|
|
3149
|
+
* `ECONNRESET` on a keep-alive pool is the classic idle-socket race — the
|
|
3150
|
+
* server closes a pooled connection while the client dispatches onto it. It
|
|
3151
|
+
* is transient by construction and safe to retry on an idempotent GET.
|
|
3152
|
+
*/
|
|
3153
|
+
const TRANSIENT_NETWORK_RETRY_ATTEMPTS = 3;
|
|
3154
|
+
/** Base backoff (ms); doubled per attempt with full jitter. */
|
|
3155
|
+
const TRANSIENT_NETWORK_RETRY_BASE_MS = 120;
|
|
3156
|
+
/** Full-jitter backoff so concurrent fan-out retries do not resonate. */
|
|
3157
|
+
function transientRetryDelayMs(attempt) {
|
|
3158
|
+
const ceiling = TRANSIENT_NETWORK_RETRY_BASE_MS * 2 ** attempt;
|
|
3159
|
+
return Math.floor(Math.random() * ceiling);
|
|
3160
|
+
}
|
|
2722
3161
|
const log$l = (message, options = { type: "info" }) => {
|
|
2723
3162
|
log$m(message, { ...options, source: "AlpacaMarketDataAPI" });
|
|
2724
3163
|
};
|
|
@@ -3229,11 +3668,42 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
3229
3668
|
// can't overrun Alpaca's server-side rate limit. Prior to this, parallel
|
|
3230
3669
|
// historical-bar fan-out produced ~125 server-side 429s per minute.
|
|
3231
3670
|
await rateLimiters.alpaca.acquire();
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3671
|
+
// Retry ONLY transient connection faults, and only on GET (every
|
|
3672
|
+
// market-data read here is idempotent). A non-2xx response is a real
|
|
3673
|
+
// answer from Alpaca and is never retried — that path still throws on
|
|
3674
|
+
// the first attempt exactly as before.
|
|
3675
|
+
let response;
|
|
3676
|
+
let lastNetworkError;
|
|
3677
|
+
for (let attempt = 0; attempt < TRANSIENT_NETWORK_RETRY_ATTEMPTS; attempt += 1) {
|
|
3678
|
+
try {
|
|
3679
|
+
response = await fetch(url.toString(), {
|
|
3680
|
+
method,
|
|
3681
|
+
headers: this.headers,
|
|
3682
|
+
signal: createTimeoutSignal(DEFAULT_TIMEOUTS.ALPACA_API),
|
|
3683
|
+
});
|
|
3684
|
+
break;
|
|
3685
|
+
}
|
|
3686
|
+
catch (networkErr) {
|
|
3687
|
+
lastNetworkError = networkErr;
|
|
3688
|
+
const retryable = method === "GET" &&
|
|
3689
|
+
isTransientNetworkError(networkErr) &&
|
|
3690
|
+
attempt < TRANSIENT_NETWORK_RETRY_ATTEMPTS - 1;
|
|
3691
|
+
if (!retryable) {
|
|
3692
|
+
throw networkErr;
|
|
3693
|
+
}
|
|
3694
|
+
const delayMs = transientRetryDelayMs(attempt);
|
|
3695
|
+
log$l(`Transient network fault on ${endpoint} (attempt ${attempt + 1}/${TRANSIENT_NETWORK_RETRY_ATTEMPTS}); retrying in ${delayMs}ms`, { type: "warn" });
|
|
3696
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
3697
|
+
// Re-acquire the rate-limit token so a retry storm cannot overrun
|
|
3698
|
+
// Alpaca's server-side limit.
|
|
3699
|
+
await rateLimiters.alpaca.acquire();
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
if (!response) {
|
|
3703
|
+
throw lastNetworkError instanceof Error
|
|
3704
|
+
? lastNetworkError
|
|
3705
|
+
: new Error(`Market Data API request failed for ${endpoint}`);
|
|
3706
|
+
}
|
|
3237
3707
|
if (!response.ok) {
|
|
3238
3708
|
const errorText = await response.text();
|
|
3239
3709
|
log$l(`Market Data API error (${response.status}): ${errorText}`, {
|
|
@@ -3965,419 +4435,6 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
3965
4435
|
// Export the singleton instance
|
|
3966
4436
|
const marketDataAPI = AlpacaMarketDataAPI.getInstance();
|
|
3967
4437
|
|
|
3968
|
-
const DEFAULT_RETRY_CONFIG = {
|
|
3969
|
-
maxRetries: 3,
|
|
3970
|
-
baseDelayMs: 1000,
|
|
3971
|
-
maxDelayMs: 30000,
|
|
3972
|
-
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
3973
|
-
retryOnNetworkError: true,
|
|
3974
|
-
};
|
|
3975
|
-
/**
|
|
3976
|
-
* Node.js / undici / system error codes that represent transient network
|
|
3977
|
-
* conditions. Present on `error.code` for net/http/dns/undici errors.
|
|
3978
|
-
*/
|
|
3979
|
-
const RETRYABLE_ERROR_CODES = new Set([
|
|
3980
|
-
"ETIMEDOUT",
|
|
3981
|
-
"ESOCKETTIMEDOUT",
|
|
3982
|
-
"ECONNRESET",
|
|
3983
|
-
"ECONNREFUSED",
|
|
3984
|
-
"EHOSTUNREACH",
|
|
3985
|
-
"ENETUNREACH",
|
|
3986
|
-
"EAI_AGAIN",
|
|
3987
|
-
"EPIPE",
|
|
3988
|
-
"ECONNABORTED",
|
|
3989
|
-
"ENOTFOUND",
|
|
3990
|
-
"UND_ERR_CONNECT_TIMEOUT",
|
|
3991
|
-
"UND_ERR_HEADERS_TIMEOUT",
|
|
3992
|
-
"UND_ERR_BODY_TIMEOUT",
|
|
3993
|
-
"UND_ERR_SOCKET",
|
|
3994
|
-
"UND_ERR_CLOSED",
|
|
3995
|
-
"UND_ERR_REQ_CONTENT_LENGTH_MISMATCH",
|
|
3996
|
-
]);
|
|
3997
|
-
/**
|
|
3998
|
-
* Error constructor names / `error.name` values that indicate transient
|
|
3999
|
-
* abort / timeout conditions.
|
|
4000
|
-
*/
|
|
4001
|
-
const RETRYABLE_ERROR_NAMES = new Set([
|
|
4002
|
-
"AbortError",
|
|
4003
|
-
"TimeoutError",
|
|
4004
|
-
"FetchError",
|
|
4005
|
-
"RequestTimeoutError",
|
|
4006
|
-
"ConnectTimeoutError",
|
|
4007
|
-
"HeadersTimeoutError",
|
|
4008
|
-
"BodyTimeoutError",
|
|
4009
|
-
]);
|
|
4010
|
-
/**
|
|
4011
|
-
* Message-pattern fallback for libraries that discard error codes/names but
|
|
4012
|
-
* preserve text (e.g., some Apollo/axios wrappers).
|
|
4013
|
-
*/
|
|
4014
|
-
const RETRYABLE_MESSAGE_PATTERNS = [
|
|
4015
|
-
/aborted/i,
|
|
4016
|
-
/timeout/i,
|
|
4017
|
-
/timed out/i,
|
|
4018
|
-
/network error/i,
|
|
4019
|
-
/socket hang up/i,
|
|
4020
|
-
/connection (reset|refused|closed)/i,
|
|
4021
|
-
/ECONNRESET/,
|
|
4022
|
-
/ETIMEDOUT/,
|
|
4023
|
-
/ECONNREFUSED/,
|
|
4024
|
-
/EAI_AGAIN/,
|
|
4025
|
-
/UND_ERR_/,
|
|
4026
|
-
];
|
|
4027
|
-
/**
|
|
4028
|
-
* Walks the `error.cause` chain (capped to avoid cycles) and tests whether
|
|
4029
|
-
* any link along the chain looks like a transient network error. Modern APIs
|
|
4030
|
-
* (undici, fetch, Apollo Client 3.8+) wrap the root network failure as a
|
|
4031
|
-
* `.cause`, so the surface `Error` may report a generic message while the
|
|
4032
|
-
* actionable signal lives one or more levels deeper.
|
|
4033
|
-
*
|
|
4034
|
-
* Exported for use by downstream consumers (engine services, per-call catch
|
|
4035
|
-
* blocks, application-level loggers) that need to demote recoverable
|
|
4036
|
-
* transient errors from ERROR to WARN. Aligns the whole stack on a single
|
|
4037
|
-
* canonical classifier so MassiveAPI, AlpacaAPI, and application-layer
|
|
4038
|
-
* retry handlers all treat the same network blips identically.
|
|
4039
|
-
*/
|
|
4040
|
-
function isTransientNetworkError(error) {
|
|
4041
|
-
const MAX_CAUSE_DEPTH = 6;
|
|
4042
|
-
let current = error;
|
|
4043
|
-
for (let depth = 0; depth < MAX_CAUSE_DEPTH && current; depth++) {
|
|
4044
|
-
if (current instanceof Error || typeof current === "object") {
|
|
4045
|
-
const err = current;
|
|
4046
|
-
if (typeof err.name === "string" && RETRYABLE_ERROR_NAMES.has(err.name)) {
|
|
4047
|
-
return true;
|
|
4048
|
-
}
|
|
4049
|
-
if (typeof err.code === "string" && RETRYABLE_ERROR_CODES.has(err.code)) {
|
|
4050
|
-
return true;
|
|
4051
|
-
}
|
|
4052
|
-
if (typeof err.message === "string") {
|
|
4053
|
-
for (const pattern of RETRYABLE_MESSAGE_PATTERNS) {
|
|
4054
|
-
if (pattern.test(err.message)) {
|
|
4055
|
-
return true;
|
|
4056
|
-
}
|
|
4057
|
-
}
|
|
4058
|
-
}
|
|
4059
|
-
current = err.cause;
|
|
4060
|
-
}
|
|
4061
|
-
else {
|
|
4062
|
-
break;
|
|
4063
|
-
}
|
|
4064
|
-
}
|
|
4065
|
-
return false;
|
|
4066
|
-
}
|
|
4067
|
-
/**
|
|
4068
|
-
* Analyzes an error and determines if it's retryable.
|
|
4069
|
-
* @param error - The error to analyze
|
|
4070
|
-
* @param response - Optional Response object for HTTP errors
|
|
4071
|
-
* @param config - Retry configuration
|
|
4072
|
-
* @returns Structured error details
|
|
4073
|
-
*/
|
|
4074
|
-
function analyzeError(error, response, config) {
|
|
4075
|
-
// Handle Response objects with error status codes
|
|
4076
|
-
if (response && !response.ok) {
|
|
4077
|
-
const status = response.status;
|
|
4078
|
-
// Rate limit errors - always retryable
|
|
4079
|
-
if (status === 429) {
|
|
4080
|
-
const retryAfterHeader = response.headers.get("Retry-After");
|
|
4081
|
-
const retryAfter = retryAfterHeader
|
|
4082
|
-
? parseInt(retryAfterHeader, 10) * 1000
|
|
4083
|
-
: undefined;
|
|
4084
|
-
return {
|
|
4085
|
-
type: "RATE_LIMIT",
|
|
4086
|
-
reason: "Rate limit exceeded",
|
|
4087
|
-
status,
|
|
4088
|
-
retryAfter,
|
|
4089
|
-
isRetryable: true,
|
|
4090
|
-
};
|
|
4091
|
-
}
|
|
4092
|
-
// Authentication errors - never retry
|
|
4093
|
-
if (status === 401 || status === 403) {
|
|
4094
|
-
return {
|
|
4095
|
-
type: "AUTH_ERROR",
|
|
4096
|
-
reason: status === 401
|
|
4097
|
-
? "Authentication failed - invalid credentials"
|
|
4098
|
-
: "Access forbidden - insufficient permissions",
|
|
4099
|
-
status,
|
|
4100
|
-
isRetryable: false,
|
|
4101
|
-
};
|
|
4102
|
-
}
|
|
4103
|
-
// Server errors - check if in retryable list
|
|
4104
|
-
if (status >= 500 && status < 600) {
|
|
4105
|
-
return {
|
|
4106
|
-
type: "SERVER_ERROR",
|
|
4107
|
-
reason: `Server error (${status})`,
|
|
4108
|
-
status,
|
|
4109
|
-
isRetryable: config.retryableStatusCodes.includes(status),
|
|
4110
|
-
};
|
|
4111
|
-
}
|
|
4112
|
-
// Other client errors - never retry
|
|
4113
|
-
if (status >= 400 && status < 500) {
|
|
4114
|
-
return {
|
|
4115
|
-
type: "CLIENT_ERROR",
|
|
4116
|
-
reason: `Client error (${status})`,
|
|
4117
|
-
status,
|
|
4118
|
-
isRetryable: false,
|
|
4119
|
-
};
|
|
4120
|
-
}
|
|
4121
|
-
}
|
|
4122
|
-
// Handle network errors (TypeError from fetch API)
|
|
4123
|
-
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
4124
|
-
return {
|
|
4125
|
-
type: "NETWORK_ERROR",
|
|
4126
|
-
reason: "Network connectivity issue",
|
|
4127
|
-
status: null,
|
|
4128
|
-
isRetryable: config.retryOnNetworkError,
|
|
4129
|
-
};
|
|
4130
|
-
}
|
|
4131
|
-
// Handle transient network conditions: AbortError, TimeoutError,
|
|
4132
|
-
// Node/undici error codes (ETIMEDOUT, ECONNRESET, UND_ERR_*), and
|
|
4133
|
-
// wrapped failures exposed via error.cause. This catches the broad class
|
|
4134
|
-
// of infrastructure flakes that the TypeError-only check above misses.
|
|
4135
|
-
if (isTransientNetworkError(error)) {
|
|
4136
|
-
const reason = error instanceof Error ? error.message : "Transient network error";
|
|
4137
|
-
return {
|
|
4138
|
-
type: "NETWORK_ERROR",
|
|
4139
|
-
reason,
|
|
4140
|
-
status: null,
|
|
4141
|
-
isRetryable: config.retryOnNetworkError,
|
|
4142
|
-
};
|
|
4143
|
-
}
|
|
4144
|
-
// Handle error objects with messages
|
|
4145
|
-
if (error instanceof Error) {
|
|
4146
|
-
// Parse error messages that might contain status information
|
|
4147
|
-
if (error.message.includes("429") || error.message.includes("RATE_LIMIT")) {
|
|
4148
|
-
const match = error.message.match(/RATE_LIMIT: 429:(\d+)/);
|
|
4149
|
-
const retryAfter = match ? parseInt(match[1], 10) : undefined;
|
|
4150
|
-
return {
|
|
4151
|
-
type: "RATE_LIMIT",
|
|
4152
|
-
reason: "Rate limit exceeded",
|
|
4153
|
-
status: 429,
|
|
4154
|
-
retryAfter,
|
|
4155
|
-
isRetryable: true,
|
|
4156
|
-
};
|
|
4157
|
-
}
|
|
4158
|
-
if (error.message.includes("401") ||
|
|
4159
|
-
error.message.includes("403") ||
|
|
4160
|
-
error.message.includes("AUTH_ERROR")) {
|
|
4161
|
-
const status = error.message.includes("401") ? 401 : 403;
|
|
4162
|
-
return {
|
|
4163
|
-
type: "AUTH_ERROR",
|
|
4164
|
-
reason: `Authentication error (${status})`,
|
|
4165
|
-
status,
|
|
4166
|
-
isRetryable: false,
|
|
4167
|
-
};
|
|
4168
|
-
}
|
|
4169
|
-
if (error.message.includes("SERVER_ERROR") ||
|
|
4170
|
-
error.message.match(/50[0-9]/)) {
|
|
4171
|
-
const statusMatch = error.message.match(/50[0-9]/);
|
|
4172
|
-
const status = statusMatch ? parseInt(statusMatch[0], 10) : 500;
|
|
4173
|
-
return {
|
|
4174
|
-
type: "SERVER_ERROR",
|
|
4175
|
-
reason: `Server error (${status})`,
|
|
4176
|
-
status,
|
|
4177
|
-
isRetryable: config.retryableStatusCodes.includes(status),
|
|
4178
|
-
};
|
|
4179
|
-
}
|
|
4180
|
-
if (error.message.includes("network") ||
|
|
4181
|
-
error.message.includes("NETWORK_ERROR")) {
|
|
4182
|
-
return {
|
|
4183
|
-
type: "NETWORK_ERROR",
|
|
4184
|
-
reason: error.message,
|
|
4185
|
-
status: null,
|
|
4186
|
-
isRetryable: config.retryOnNetworkError,
|
|
4187
|
-
};
|
|
4188
|
-
}
|
|
4189
|
-
}
|
|
4190
|
-
// Unknown error - not retryable by default for safety
|
|
4191
|
-
return {
|
|
4192
|
-
type: "UNKNOWN",
|
|
4193
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
4194
|
-
status: null,
|
|
4195
|
-
isRetryable: false,
|
|
4196
|
-
};
|
|
4197
|
-
}
|
|
4198
|
-
/**
|
|
4199
|
-
* Calculates the delay before the next retry attempt using exponential backoff with jitter.
|
|
4200
|
-
* @param attempt - Current attempt number (1-indexed)
|
|
4201
|
-
* @param baseDelay - Base delay in milliseconds
|
|
4202
|
-
* @param maxDelay - Maximum delay in milliseconds
|
|
4203
|
-
* @returns Delay in milliseconds
|
|
4204
|
-
*/
|
|
4205
|
-
function calculateBackoff(attempt, baseDelay, maxDelay) {
|
|
4206
|
-
// Exponential backoff: baseDelay * 2^(attempt-1)
|
|
4207
|
-
const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
|
|
4208
|
-
// Cap at maxDelay
|
|
4209
|
-
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
4210
|
-
// Add jitter (random value between 0% and 25% of the delay)
|
|
4211
|
-
const jitter = Math.random() * cappedDelay * 0.25;
|
|
4212
|
-
return Math.floor(cappedDelay + jitter);
|
|
4213
|
-
}
|
|
4214
|
-
/**
|
|
4215
|
-
* Wraps an async function with retry logic and exponential backoff.
|
|
4216
|
-
*
|
|
4217
|
-
* This utility handles transient errors in external API calls by automatically retrying
|
|
4218
|
-
* failed requests with intelligent backoff strategies. It respects rate limit headers,
|
|
4219
|
-
* fails fast on non-retryable errors, and provides detailed logging.
|
|
4220
|
-
*
|
|
4221
|
-
* @template T - The return type of the wrapped function
|
|
4222
|
-
* @param fn - The async function to wrap with retry logic
|
|
4223
|
-
* @param config - Retry configuration (merged with defaults)
|
|
4224
|
-
* @param label - A descriptive label for logging (e.g., 'Massive.fetchTickerInfo')
|
|
4225
|
-
* @returns A promise that resolves to the function's return value
|
|
4226
|
-
* @throws The last error encountered if all retries are exhausted
|
|
4227
|
-
*
|
|
4228
|
-
* @example
|
|
4229
|
-
* ```typescript
|
|
4230
|
-
* // Basic usage with defaults
|
|
4231
|
-
* const data = await withRetry(
|
|
4232
|
-
* async () => fetch('https://api.example.com/data'),
|
|
4233
|
-
* {},
|
|
4234
|
-
* 'ExampleAPI.fetchData'
|
|
4235
|
-
* );
|
|
4236
|
-
*
|
|
4237
|
-
* // Custom configuration for rate-limited API
|
|
4238
|
-
* const result = await withRetry(
|
|
4239
|
-
* async () => alphaVantageAPI.getQuote(symbol),
|
|
4240
|
-
* {
|
|
4241
|
-
* maxRetries: 5,
|
|
4242
|
-
* baseDelayMs: 5000,
|
|
4243
|
-
* maxDelayMs: 60000,
|
|
4244
|
-
* onRetry: (attempt, error) => {
|
|
4245
|
-
* getLogger().info(`Retry ${attempt} after error:`, error);
|
|
4246
|
-
* }
|
|
4247
|
-
* },
|
|
4248
|
-
* 'AlphaVantage.getQuote'
|
|
4249
|
-
* );
|
|
4250
|
-
* ```
|
|
4251
|
-
*/
|
|
4252
|
-
async function withRetry(fn, config = {}, label = "unknown") {
|
|
4253
|
-
const fullConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
4254
|
-
let lastError;
|
|
4255
|
-
for (let attempt = 1; attempt <= fullConfig.maxRetries; attempt++) {
|
|
4256
|
-
try {
|
|
4257
|
-
const result = await fn();
|
|
4258
|
-
// If we succeeded after retries, log it
|
|
4259
|
-
if (attempt > 1) {
|
|
4260
|
-
getLogger().info(`[${label}] Succeeded on attempt ${attempt}/${fullConfig.maxRetries}`);
|
|
4261
|
-
}
|
|
4262
|
-
return result;
|
|
4263
|
-
}
|
|
4264
|
-
catch (error) {
|
|
4265
|
-
lastError = error;
|
|
4266
|
-
// If this is the last attempt, throw the error.
|
|
4267
|
-
// Transient network classes (undici/fetch timeouts, ECONNRESET,
|
|
4268
|
-
// AbortError, etc.) are self-healing at the upstream retry layer —
|
|
4269
|
-
// the caller re-invokes on the next refresh/poll tick. Logging them
|
|
4270
|
-
// at ERROR produces alert noise that does not represent actionable
|
|
4271
|
-
// failures. Demote the transient class to WARN with a recovery hint;
|
|
4272
|
-
// reserve ERROR for non-transient final failures (auth, schema,
|
|
4273
|
-
// contract violations, unknown classes).
|
|
4274
|
-
if (attempt === fullConfig.maxRetries) {
|
|
4275
|
-
const isTransient = isTransientNetworkError(error);
|
|
4276
|
-
const logMeta = {
|
|
4277
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4278
|
-
attempts: fullConfig.maxRetries,
|
|
4279
|
-
timestamp: new Date().toISOString(),
|
|
4280
|
-
...(isTransient
|
|
4281
|
-
? {
|
|
4282
|
-
transient: true,
|
|
4283
|
-
recoveryHint: "Upstream caller should retry on next cycle",
|
|
4284
|
-
}
|
|
4285
|
-
: {}),
|
|
4286
|
-
};
|
|
4287
|
-
if (isTransient) {
|
|
4288
|
-
getLogger().warn(`[${label}] Failed after ${fullConfig.maxRetries} attempts (transient)`, logMeta);
|
|
4289
|
-
}
|
|
4290
|
-
else {
|
|
4291
|
-
getLogger().error(`[${label}] Failed after ${fullConfig.maxRetries} attempts`, logMeta);
|
|
4292
|
-
}
|
|
4293
|
-
throw error;
|
|
4294
|
-
}
|
|
4295
|
-
// Analyze the error to determine if we should retry
|
|
4296
|
-
const response = error instanceof Response ? error : null;
|
|
4297
|
-
const errorDetails = analyzeError(error, response, fullConfig);
|
|
4298
|
-
// If error is not retryable, fail immediately
|
|
4299
|
-
if (!errorDetails.isRetryable) {
|
|
4300
|
-
getLogger().error(`[${label}] Non-retryable error (${errorDetails.type})`, {
|
|
4301
|
-
reason: errorDetails.reason,
|
|
4302
|
-
status: errorDetails.status,
|
|
4303
|
-
timestamp: new Date().toISOString(),
|
|
4304
|
-
});
|
|
4305
|
-
throw error;
|
|
4306
|
-
}
|
|
4307
|
-
// Calculate delay for next retry
|
|
4308
|
-
let delayMs;
|
|
4309
|
-
if (errorDetails.type === "RATE_LIMIT" && errorDetails.retryAfter) {
|
|
4310
|
-
// Use Retry-After header if available
|
|
4311
|
-
delayMs = errorDetails.retryAfter;
|
|
4312
|
-
}
|
|
4313
|
-
else if (errorDetails.type === "RATE_LIMIT") {
|
|
4314
|
-
// For rate limits without Retry-After, use a longer minimum delay
|
|
4315
|
-
delayMs = Math.max(calculateBackoff(attempt, fullConfig.baseDelayMs, fullConfig.maxDelayMs), 5000);
|
|
4316
|
-
}
|
|
4317
|
-
else {
|
|
4318
|
-
// Standard exponential backoff with jitter
|
|
4319
|
-
delayMs = calculateBackoff(attempt, fullConfig.baseDelayMs, fullConfig.maxDelayMs);
|
|
4320
|
-
}
|
|
4321
|
-
// Log the retry attempt
|
|
4322
|
-
getLogger().warn(`[${label}] Attempt ${attempt}/${fullConfig.maxRetries} failed: ${errorDetails.reason}. Retrying in ${delayMs}ms...`, {
|
|
4323
|
-
attemptNumber: attempt,
|
|
4324
|
-
totalRetries: fullConfig.maxRetries,
|
|
4325
|
-
errorType: errorDetails.type,
|
|
4326
|
-
httpStatus: errorDetails.status,
|
|
4327
|
-
retryDelay: delayMs,
|
|
4328
|
-
timestamp: new Date().toISOString(),
|
|
4329
|
-
});
|
|
4330
|
-
// Call the optional retry callback
|
|
4331
|
-
if (fullConfig.onRetry) {
|
|
4332
|
-
fullConfig.onRetry(attempt, error);
|
|
4333
|
-
}
|
|
4334
|
-
// Wait before retrying
|
|
4335
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
4336
|
-
}
|
|
4337
|
-
}
|
|
4338
|
-
// This should never be reached due to the throw in the last attempt,
|
|
4339
|
-
// but TypeScript needs this to satisfy the return type
|
|
4340
|
-
throw lastError;
|
|
4341
|
-
}
|
|
4342
|
-
/**
|
|
4343
|
-
* API-specific retry configurations for different external services.
|
|
4344
|
-
* These configurations are tuned based on each API's rate limits and characteristics.
|
|
4345
|
-
*/
|
|
4346
|
-
const API_RETRY_CONFIGS = {
|
|
4347
|
-
/** Massive.com API - 5 requests/second rate limit */
|
|
4348
|
-
MASSIVE: {
|
|
4349
|
-
maxRetries: 3,
|
|
4350
|
-
baseDelayMs: 1000,
|
|
4351
|
-
maxDelayMs: 30000,
|
|
4352
|
-
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
4353
|
-
retryOnNetworkError: true,
|
|
4354
|
-
},
|
|
4355
|
-
/** Alpha Vantage API - 5 requests/minute rate limit (more strict) */
|
|
4356
|
-
ALPHA_VANTAGE: {
|
|
4357
|
-
maxRetries: 5,
|
|
4358
|
-
baseDelayMs: 5000,
|
|
4359
|
-
maxDelayMs: 60000,
|
|
4360
|
-
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
4361
|
-
retryOnNetworkError: true,
|
|
4362
|
-
},
|
|
4363
|
-
/** Alpaca API - generally reliable, shorter retry window */
|
|
4364
|
-
ALPACA: {
|
|
4365
|
-
maxRetries: 3,
|
|
4366
|
-
baseDelayMs: 1000,
|
|
4367
|
-
maxDelayMs: 30000,
|
|
4368
|
-
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
4369
|
-
retryOnNetworkError: true,
|
|
4370
|
-
},
|
|
4371
|
-
/** Generic crypto API configuration */
|
|
4372
|
-
CRYPTO: {
|
|
4373
|
-
maxRetries: 3,
|
|
4374
|
-
baseDelayMs: 1000,
|
|
4375
|
-
maxDelayMs: 30000,
|
|
4376
|
-
retryableStatusCodes: [429, 500, 502, 503, 504],
|
|
4377
|
-
retryOnNetworkError: true,
|
|
4378
|
-
},
|
|
4379
|
-
};
|
|
4380
|
-
|
|
4381
4438
|
const limitPriceSlippagePercent100 = 0.1; // 0.1%
|
|
4382
4439
|
/**
|
|
4383
4440
|
* Alpaca's maximum page size for GET /orders — also our explicit default.
|
|
@@ -14561,17 +14618,17 @@ var hasRequiredBrowser;
|
|
|
14561
14618
|
function requireBrowser () {
|
|
14562
14619
|
if (hasRequiredBrowser) return browser.exports;
|
|
14563
14620
|
hasRequiredBrowser = 1;
|
|
14564
|
-
(function (module, exports) {
|
|
14621
|
+
(function (module, exports$1) {
|
|
14565
14622
|
/**
|
|
14566
14623
|
* This is the web browser implementation of `debug()`.
|
|
14567
14624
|
*/
|
|
14568
14625
|
|
|
14569
|
-
exports.formatArgs = formatArgs;
|
|
14570
|
-
exports.save = save;
|
|
14571
|
-
exports.load = load;
|
|
14572
|
-
exports.useColors = useColors;
|
|
14573
|
-
exports.storage = localstorage();
|
|
14574
|
-
exports.destroy = (() => {
|
|
14626
|
+
exports$1.formatArgs = formatArgs;
|
|
14627
|
+
exports$1.save = save;
|
|
14628
|
+
exports$1.load = load;
|
|
14629
|
+
exports$1.useColors = useColors;
|
|
14630
|
+
exports$1.storage = localstorage();
|
|
14631
|
+
exports$1.destroy = (() => {
|
|
14575
14632
|
let warned = false;
|
|
14576
14633
|
|
|
14577
14634
|
return () => {
|
|
@@ -14586,7 +14643,7 @@ function requireBrowser () {
|
|
|
14586
14643
|
* Colors.
|
|
14587
14644
|
*/
|
|
14588
14645
|
|
|
14589
|
-
exports.colors = [
|
|
14646
|
+
exports$1.colors = [
|
|
14590
14647
|
'#0000CC',
|
|
14591
14648
|
'#0000FF',
|
|
14592
14649
|
'#0033CC',
|
|
@@ -14751,7 +14808,7 @@ function requireBrowser () {
|
|
|
14751
14808
|
*
|
|
14752
14809
|
* @api public
|
|
14753
14810
|
*/
|
|
14754
|
-
exports.log = console.debug || console.log || (() => {});
|
|
14811
|
+
exports$1.log = console.debug || console.log || (() => {});
|
|
14755
14812
|
|
|
14756
14813
|
/**
|
|
14757
14814
|
* Save `namespaces`.
|
|
@@ -14762,9 +14819,9 @@ function requireBrowser () {
|
|
|
14762
14819
|
function save(namespaces) {
|
|
14763
14820
|
try {
|
|
14764
14821
|
if (namespaces) {
|
|
14765
|
-
exports.storage.setItem('debug', namespaces);
|
|
14822
|
+
exports$1.storage.setItem('debug', namespaces);
|
|
14766
14823
|
} else {
|
|
14767
|
-
exports.storage.removeItem('debug');
|
|
14824
|
+
exports$1.storage.removeItem('debug');
|
|
14768
14825
|
}
|
|
14769
14826
|
} catch (error) {
|
|
14770
14827
|
// Swallow
|
|
@@ -14781,7 +14838,7 @@ function requireBrowser () {
|
|
|
14781
14838
|
function load() {
|
|
14782
14839
|
let r;
|
|
14783
14840
|
try {
|
|
14784
|
-
r = exports.storage.getItem('debug') || exports.storage.getItem('DEBUG') ;
|
|
14841
|
+
r = exports$1.storage.getItem('debug') || exports$1.storage.getItem('DEBUG') ;
|
|
14785
14842
|
} catch (error) {
|
|
14786
14843
|
// Swallow
|
|
14787
14844
|
// XXX (@Qix-) should we be logging these?
|
|
@@ -14817,7 +14874,7 @@ function requireBrowser () {
|
|
|
14817
14874
|
}
|
|
14818
14875
|
}
|
|
14819
14876
|
|
|
14820
|
-
module.exports = requireCommon()(exports);
|
|
14877
|
+
module.exports = requireCommon()(exports$1);
|
|
14821
14878
|
|
|
14822
14879
|
const {formatters} = module.exports;
|
|
14823
14880
|
|
|
@@ -15006,7 +15063,7 @@ var hasRequiredNode$1;
|
|
|
15006
15063
|
function requireNode$1 () {
|
|
15007
15064
|
if (hasRequiredNode$1) return node$1.exports;
|
|
15008
15065
|
hasRequiredNode$1 = 1;
|
|
15009
|
-
(function (module, exports) {
|
|
15066
|
+
(function (module, exports$1) {
|
|
15010
15067
|
const tty = require$$1$1;
|
|
15011
15068
|
const util = require$$1$2;
|
|
15012
15069
|
|
|
@@ -15014,13 +15071,13 @@ function requireNode$1 () {
|
|
|
15014
15071
|
* This is the Node.js implementation of `debug()`.
|
|
15015
15072
|
*/
|
|
15016
15073
|
|
|
15017
|
-
exports.init = init;
|
|
15018
|
-
exports.log = log;
|
|
15019
|
-
exports.formatArgs = formatArgs;
|
|
15020
|
-
exports.save = save;
|
|
15021
|
-
exports.load = load;
|
|
15022
|
-
exports.useColors = useColors;
|
|
15023
|
-
exports.destroy = util.deprecate(
|
|
15074
|
+
exports$1.init = init;
|
|
15075
|
+
exports$1.log = log;
|
|
15076
|
+
exports$1.formatArgs = formatArgs;
|
|
15077
|
+
exports$1.save = save;
|
|
15078
|
+
exports$1.load = load;
|
|
15079
|
+
exports$1.useColors = useColors;
|
|
15080
|
+
exports$1.destroy = util.deprecate(
|
|
15024
15081
|
() => {},
|
|
15025
15082
|
'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'
|
|
15026
15083
|
);
|
|
@@ -15029,7 +15086,7 @@ function requireNode$1 () {
|
|
|
15029
15086
|
* Colors.
|
|
15030
15087
|
*/
|
|
15031
15088
|
|
|
15032
|
-
exports.colors = [6, 2, 3, 4, 5, 1];
|
|
15089
|
+
exports$1.colors = [6, 2, 3, 4, 5, 1];
|
|
15033
15090
|
|
|
15034
15091
|
try {
|
|
15035
15092
|
// Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json)
|
|
@@ -15037,7 +15094,7 @@ function requireNode$1 () {
|
|
|
15037
15094
|
const supportsColor = requireSupportsColor();
|
|
15038
15095
|
|
|
15039
15096
|
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
|
|
15040
|
-
exports.colors = [
|
|
15097
|
+
exports$1.colors = [
|
|
15041
15098
|
20,
|
|
15042
15099
|
21,
|
|
15043
15100
|
26,
|
|
@@ -15126,7 +15183,7 @@ function requireNode$1 () {
|
|
|
15126
15183
|
* $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js
|
|
15127
15184
|
*/
|
|
15128
15185
|
|
|
15129
|
-
exports.inspectOpts = Object.keys(process.env).filter(key => {
|
|
15186
|
+
exports$1.inspectOpts = Object.keys(process.env).filter(key => {
|
|
15130
15187
|
return /^debug_/i.test(key);
|
|
15131
15188
|
}).reduce((obj, key) => {
|
|
15132
15189
|
// Camel-case
|
|
@@ -15158,8 +15215,8 @@ function requireNode$1 () {
|
|
|
15158
15215
|
*/
|
|
15159
15216
|
|
|
15160
15217
|
function useColors() {
|
|
15161
|
-
return 'colors' in exports.inspectOpts ?
|
|
15162
|
-
Boolean(exports.inspectOpts.colors) :
|
|
15218
|
+
return 'colors' in exports$1.inspectOpts ?
|
|
15219
|
+
Boolean(exports$1.inspectOpts.colors) :
|
|
15163
15220
|
tty.isatty(process.stderr.fd);
|
|
15164
15221
|
}
|
|
15165
15222
|
|
|
@@ -15185,7 +15242,7 @@ function requireNode$1 () {
|
|
|
15185
15242
|
}
|
|
15186
15243
|
|
|
15187
15244
|
function getDate() {
|
|
15188
|
-
if (exports.inspectOpts.hideDate) {
|
|
15245
|
+
if (exports$1.inspectOpts.hideDate) {
|
|
15189
15246
|
return '';
|
|
15190
15247
|
}
|
|
15191
15248
|
return new Date().toISOString() + ' ';
|
|
@@ -15196,7 +15253,7 @@ function requireNode$1 () {
|
|
|
15196
15253
|
*/
|
|
15197
15254
|
|
|
15198
15255
|
function log(...args) {
|
|
15199
|
-
return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + '\n');
|
|
15256
|
+
return process.stderr.write(util.formatWithOptions(exports$1.inspectOpts, ...args) + '\n');
|
|
15200
15257
|
}
|
|
15201
15258
|
|
|
15202
15259
|
/**
|
|
@@ -15236,13 +15293,13 @@ function requireNode$1 () {
|
|
|
15236
15293
|
function init(debug) {
|
|
15237
15294
|
debug.inspectOpts = {};
|
|
15238
15295
|
|
|
15239
|
-
const keys = Object.keys(exports.inspectOpts);
|
|
15296
|
+
const keys = Object.keys(exports$1.inspectOpts);
|
|
15240
15297
|
for (let i = 0; i < keys.length; i++) {
|
|
15241
|
-
debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
|
|
15298
|
+
debug.inspectOpts[keys[i]] = exports$1.inspectOpts[keys[i]];
|
|
15242
15299
|
}
|
|
15243
15300
|
}
|
|
15244
15301
|
|
|
15245
|
-
module.exports = requireCommon()(exports);
|
|
15302
|
+
module.exports = requireCommon()(exports$1);
|
|
15246
15303
|
|
|
15247
15304
|
const {formatters} = module.exports;
|
|
15248
15305
|
|
|
@@ -15816,7 +15873,7 @@ function requireFollowRedirects () {
|
|
|
15816
15873
|
// Wraps the key/value object of protocols with redirect functionality
|
|
15817
15874
|
function wrap(protocols) {
|
|
15818
15875
|
// Default settings
|
|
15819
|
-
var exports = {
|
|
15876
|
+
var exports$1 = {
|
|
15820
15877
|
maxRedirects: 21,
|
|
15821
15878
|
maxBodyLength: 10 * 1024 * 1024,
|
|
15822
15879
|
};
|
|
@@ -15826,7 +15883,7 @@ function requireFollowRedirects () {
|
|
|
15826
15883
|
Object.keys(protocols).forEach(function (scheme) {
|
|
15827
15884
|
var protocol = scheme + ":";
|
|
15828
15885
|
var nativeProtocol = nativeProtocols[protocol] = protocols[scheme];
|
|
15829
|
-
var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol);
|
|
15886
|
+
var wrappedProtocol = exports$1[scheme] = Object.create(nativeProtocol);
|
|
15830
15887
|
|
|
15831
15888
|
// Executes a request, following redirects
|
|
15832
15889
|
function request(input, options, callback) {
|
|
@@ -15849,8 +15906,8 @@ function requireFollowRedirects () {
|
|
|
15849
15906
|
|
|
15850
15907
|
// Set defaults
|
|
15851
15908
|
options = Object.assign({
|
|
15852
|
-
maxRedirects: exports.maxRedirects,
|
|
15853
|
-
maxBodyLength: exports.maxBodyLength,
|
|
15909
|
+
maxRedirects: exports$1.maxRedirects,
|
|
15910
|
+
maxBodyLength: exports$1.maxBodyLength,
|
|
15854
15911
|
}, input, options);
|
|
15855
15912
|
options.nativeProtocols = nativeProtocols;
|
|
15856
15913
|
if (!isString(options.host) && !isString(options.hostname)) {
|
|
@@ -15875,7 +15932,7 @@ function requireFollowRedirects () {
|
|
|
15875
15932
|
get: { value: get, configurable: true, enumerable: true, writable: true },
|
|
15876
15933
|
});
|
|
15877
15934
|
});
|
|
15878
|
-
return exports;
|
|
15935
|
+
return exports$1;
|
|
15879
15936
|
}
|
|
15880
15937
|
|
|
15881
15938
|
function noop() { /* empty */ }
|
|
@@ -17481,7 +17538,7 @@ var hasRequiredLodash;
|
|
|
17481
17538
|
function requireLodash () {
|
|
17482
17539
|
if (hasRequiredLodash) return lodash$1.exports;
|
|
17483
17540
|
hasRequiredLodash = 1;
|
|
17484
|
-
(function (module, exports) {
|
|
17541
|
+
(function (module, exports$1) {
|
|
17485
17542
|
(function() {
|
|
17486
17543
|
|
|
17487
17544
|
/** Used as a safe reference for `undefined` in pre-ES5 environments. */
|
|
@@ -17912,7 +17969,7 @@ function requireLodash () {
|
|
|
17912
17969
|
var root = freeGlobal || freeSelf || Function('return this')();
|
|
17913
17970
|
|
|
17914
17971
|
/** Detect free variable `exports`. */
|
|
17915
|
-
var freeExports = exports && !exports.nodeType && exports;
|
|
17972
|
+
var freeExports = exports$1 && !exports$1.nodeType && exports$1;
|
|
17916
17973
|
|
|
17917
17974
|
/** Detect free variable `module`. */
|
|
17918
17975
|
var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module;
|
|
@@ -35742,12 +35799,12 @@ var hasRequiredIsBuffer;
|
|
|
35742
35799
|
function requireIsBuffer () {
|
|
35743
35800
|
if (hasRequiredIsBuffer) return isBuffer.exports;
|
|
35744
35801
|
hasRequiredIsBuffer = 1;
|
|
35745
|
-
(function (module, exports) {
|
|
35802
|
+
(function (module, exports$1) {
|
|
35746
35803
|
var root = require_root(),
|
|
35747
35804
|
stubFalse = requireStubFalse();
|
|
35748
35805
|
|
|
35749
35806
|
/** Detect free variable `exports`. */
|
|
35750
|
-
var freeExports = exports && !exports.nodeType && exports;
|
|
35807
|
+
var freeExports = exports$1 && !exports$1.nodeType && exports$1;
|
|
35751
35808
|
|
|
35752
35809
|
/** Detect free variable `module`. */
|
|
35753
35810
|
var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module;
|
|
@@ -35967,11 +36024,11 @@ var hasRequired_nodeUtil;
|
|
|
35967
36024
|
function require_nodeUtil () {
|
|
35968
36025
|
if (hasRequired_nodeUtil) return _nodeUtil.exports;
|
|
35969
36026
|
hasRequired_nodeUtil = 1;
|
|
35970
|
-
(function (module, exports) {
|
|
36027
|
+
(function (module, exports$1) {
|
|
35971
36028
|
var freeGlobal = require_freeGlobal();
|
|
35972
36029
|
|
|
35973
36030
|
/** Detect free variable `exports`. */
|
|
35974
|
-
var freeExports = exports && !exports.nodeType && exports;
|
|
36031
|
+
var freeExports = exports$1 && !exports$1.nodeType && exports$1;
|
|
35975
36032
|
|
|
35976
36033
|
/** Detect free variable `module`. */
|
|
35977
36034
|
var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module;
|
|
@@ -44893,7 +44950,7 @@ var hasRequiredSafeBuffer;
|
|
|
44893
44950
|
function requireSafeBuffer () {
|
|
44894
44951
|
if (hasRequiredSafeBuffer) return safeBuffer.exports;
|
|
44895
44952
|
hasRequiredSafeBuffer = 1;
|
|
44896
|
-
(function (module, exports) {
|
|
44953
|
+
(function (module, exports$1) {
|
|
44897
44954
|
/* eslint-disable node/no-deprecated-api */
|
|
44898
44955
|
var buffer = require$$0$5;
|
|
44899
44956
|
var Buffer = buffer.Buffer;
|
|
@@ -44908,8 +44965,8 @@ function requireSafeBuffer () {
|
|
|
44908
44965
|
module.exports = buffer;
|
|
44909
44966
|
} else {
|
|
44910
44967
|
// Copy properties from require('buffer')
|
|
44911
|
-
copyProps(buffer, exports);
|
|
44912
|
-
exports.Buffer = SafeBuffer;
|
|
44968
|
+
copyProps(buffer, exports$1);
|
|
44969
|
+
exports$1.Buffer = SafeBuffer;
|
|
44913
44970
|
}
|
|
44914
44971
|
|
|
44915
44972
|
function SafeBuffer (arg, encodingOrOffset, length) {
|
|
@@ -48107,22 +48164,22 @@ var hasRequiredReadable;
|
|
|
48107
48164
|
function requireReadable () {
|
|
48108
48165
|
if (hasRequiredReadable) return readable.exports;
|
|
48109
48166
|
hasRequiredReadable = 1;
|
|
48110
|
-
(function (module, exports) {
|
|
48167
|
+
(function (module, exports$1) {
|
|
48111
48168
|
var Stream = require$$0$3;
|
|
48112
48169
|
if (process.env.READABLE_STREAM === 'disable' && Stream) {
|
|
48113
48170
|
module.exports = Stream.Readable;
|
|
48114
48171
|
Object.assign(module.exports, Stream);
|
|
48115
48172
|
module.exports.Stream = Stream;
|
|
48116
48173
|
} else {
|
|
48117
|
-
exports = module.exports = require_stream_readable();
|
|
48118
|
-
exports.Stream = Stream || exports;
|
|
48119
|
-
exports.Readable = exports;
|
|
48120
|
-
exports.Writable = require_stream_writable();
|
|
48121
|
-
exports.Duplex = require_stream_duplex();
|
|
48122
|
-
exports.Transform = require_stream_transform();
|
|
48123
|
-
exports.PassThrough = require_stream_passthrough();
|
|
48124
|
-
exports.finished = requireEndOfStream();
|
|
48125
|
-
exports.pipeline = requirePipeline();
|
|
48174
|
+
exports$1 = module.exports = require_stream_readable();
|
|
48175
|
+
exports$1.Stream = Stream || exports$1;
|
|
48176
|
+
exports$1.Readable = exports$1;
|
|
48177
|
+
exports$1.Writable = require_stream_writable();
|
|
48178
|
+
exports$1.Duplex = require_stream_duplex();
|
|
48179
|
+
exports$1.Transform = require_stream_transform();
|
|
48180
|
+
exports$1.PassThrough = require_stream_passthrough();
|
|
48181
|
+
exports$1.finished = requireEndOfStream();
|
|
48182
|
+
exports$1.pipeline = requirePipeline();
|
|
48126
48183
|
}
|
|
48127
48184
|
} (readable, readable.exports));
|
|
48128
48185
|
return readable.exports;
|
|
@@ -49557,12 +49614,12 @@ var hasRequiredWebsocket;
|
|
|
49557
49614
|
function requireWebsocket () {
|
|
49558
49615
|
if (hasRequiredWebsocket) return websocket$1;
|
|
49559
49616
|
hasRequiredWebsocket = 1;
|
|
49560
|
-
(function (exports) {
|
|
49617
|
+
(function (exports$1) {
|
|
49561
49618
|
var __importDefault = (websocket$1 && websocket$1.__importDefault) || function (mod) {
|
|
49562
49619
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
49563
49620
|
};
|
|
49564
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49565
|
-
exports.AlpacaWebsocket = exports.ERROR = exports.CONN_ERROR = exports.EVENT = exports.STATE = void 0;
|
|
49621
|
+
Object.defineProperty(exports$1, "__esModule", { value: true });
|
|
49622
|
+
exports$1.AlpacaWebsocket = exports$1.ERROR = exports$1.CONN_ERROR = exports$1.EVENT = exports$1.STATE = void 0;
|
|
49566
49623
|
const events_1 = __importDefault(require$$0$4);
|
|
49567
49624
|
const ws_1 = __importDefault(requireWs());
|
|
49568
49625
|
const msgpack5_1 = __importDefault(requireMsgpack5());
|
|
@@ -49576,7 +49633,7 @@ function requireWebsocket () {
|
|
|
49576
49633
|
STATE["DISCONNECTED"] = "disconnected";
|
|
49577
49634
|
STATE["WAITING_TO_CONNECT"] = "waiting to connect";
|
|
49578
49635
|
STATE["WAITING_TO_RECONNECT"] = "waiting to reconnect";
|
|
49579
|
-
})(STATE || (exports.STATE = STATE = {}));
|
|
49636
|
+
})(STATE || (exports$1.STATE = STATE = {}));
|
|
49580
49637
|
// Client events
|
|
49581
49638
|
var EVENT;
|
|
49582
49639
|
(function (EVENT) {
|
|
@@ -49595,9 +49652,9 @@ function requireWebsocket () {
|
|
|
49595
49652
|
EVENT["CORRECTIONS"] = "corrections";
|
|
49596
49653
|
EVENT["ORDERBOOKS"] = "orderbooks";
|
|
49597
49654
|
EVENT["NEWS"] = "news";
|
|
49598
|
-
})(EVENT || (exports.EVENT = EVENT = {}));
|
|
49655
|
+
})(EVENT || (exports$1.EVENT = EVENT = {}));
|
|
49599
49656
|
// Connection errors by code
|
|
49600
|
-
exports.CONN_ERROR = new Map([
|
|
49657
|
+
exports$1.CONN_ERROR = new Map([
|
|
49601
49658
|
[400, "invalid syntax"],
|
|
49602
49659
|
[401, "not authenticated"],
|
|
49603
49660
|
[402, "auth failed"],
|
|
@@ -49616,7 +49673,7 @@ function requireWebsocket () {
|
|
|
49616
49673
|
ERROR["MISSING_SECERT_KEY"] = "missing secret key";
|
|
49617
49674
|
ERROR["MISSING_API_KEY"] = "missing api key";
|
|
49618
49675
|
ERROR["UNEXPECTED_MESSAGE"] = "unexpected message";
|
|
49619
|
-
})(ERROR || (exports.ERROR = ERROR = {}));
|
|
49676
|
+
})(ERROR || (exports$1.ERROR = ERROR = {}));
|
|
49620
49677
|
class AlpacaWebsocket extends events_1.default.EventEmitter {
|
|
49621
49678
|
constructor(options) {
|
|
49622
49679
|
super();
|
|
@@ -49773,7 +49830,7 @@ function requireWebsocket () {
|
|
|
49773
49830
|
this.updateSubscriptions(data[0]);
|
|
49774
49831
|
break;
|
|
49775
49832
|
case "error":
|
|
49776
|
-
this.emit(EVENT.CLIENT_ERROR, exports.CONN_ERROR.get(data[0].code));
|
|
49833
|
+
this.emit(EVENT.CLIENT_ERROR, exports$1.CONN_ERROR.get(data[0].code));
|
|
49777
49834
|
break;
|
|
49778
49835
|
default:
|
|
49779
49836
|
this.dataHandler(data);
|
|
@@ -49802,7 +49859,7 @@ function requireWebsocket () {
|
|
|
49802
49859
|
}
|
|
49803
49860
|
}
|
|
49804
49861
|
}
|
|
49805
|
-
exports.AlpacaWebsocket = AlpacaWebsocket;
|
|
49862
|
+
exports$1.AlpacaWebsocket = AlpacaWebsocket;
|
|
49806
49863
|
} (websocket$1));
|
|
49807
49864
|
return websocket$1;
|
|
49808
49865
|
}
|
|
@@ -50436,8 +50493,8 @@ var hasRequiredWebsockets;
|
|
|
50436
50493
|
function requireWebsockets () {
|
|
50437
50494
|
if (hasRequiredWebsockets) return websockets;
|
|
50438
50495
|
hasRequiredWebsockets = 1;
|
|
50439
|
-
(function (exports) {
|
|
50440
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50496
|
+
(function (exports$1) {
|
|
50497
|
+
Object.defineProperty(exports$1, "__esModule", { value: true });
|
|
50441
50498
|
const events = require$$0$4;
|
|
50442
50499
|
const WebSocket = requireWs();
|
|
50443
50500
|
const entity = requireEntity();
|
|
@@ -50452,7 +50509,7 @@ function requireWebsockets () {
|
|
|
50452
50509
|
STATE.DISCONNECTED = "disconnected";
|
|
50453
50510
|
STATE.WAITING_TO_CONNECT = "waiting to connect";
|
|
50454
50511
|
STATE.WAITING_TO_RECONNECT = "waiting to reconnect";
|
|
50455
|
-
})((STATE = exports.STATE || (exports.STATE = {})));
|
|
50512
|
+
})((STATE = exports$1.STATE || (exports$1.STATE = {})));
|
|
50456
50513
|
// Client events
|
|
50457
50514
|
var EVENT;
|
|
50458
50515
|
(function (EVENT) {
|
|
@@ -50466,7 +50523,7 @@ function requireWebsockets () {
|
|
|
50466
50523
|
EVENT.STOCK_QUOTES = "stock_quotes";
|
|
50467
50524
|
EVENT.STOCK_AGG_SEC = "stock_agg_sec";
|
|
50468
50525
|
EVENT.STOCK_AGG_MIN = "stock_agg_min";
|
|
50469
|
-
})((EVENT = exports.EVENT || (exports.EVENT = {})));
|
|
50526
|
+
})((EVENT = exports$1.EVENT || (exports$1.EVENT = {})));
|
|
50470
50527
|
// Connection errors Each of these will also emit EVENT.ERROR
|
|
50471
50528
|
var ERROR;
|
|
50472
50529
|
(function (ERROR) {
|
|
@@ -50475,7 +50532,7 @@ function requireWebsockets () {
|
|
|
50475
50532
|
ERROR.MISSING_API_KEY = "missing api key";
|
|
50476
50533
|
ERROR.MISSING_SECRET_KEY = "missing secret key";
|
|
50477
50534
|
ERROR.UNKNOWN = "unknown error";
|
|
50478
|
-
})((ERROR = exports.ERROR || (exports.ERROR = {})));
|
|
50535
|
+
})((ERROR = exports$1.ERROR || (exports$1.ERROR = {})));
|
|
50479
50536
|
/**
|
|
50480
50537
|
* AlpacaStreamClient manages a connection to Alpaca's websocket api
|
|
50481
50538
|
*/
|
|
@@ -50788,7 +50845,7 @@ function requireWebsockets () {
|
|
|
50788
50845
|
}
|
|
50789
50846
|
}
|
|
50790
50847
|
}
|
|
50791
|
-
exports.AlpacaStreamClient = AlpacaStreamClient;
|
|
50848
|
+
exports$1.AlpacaStreamClient = AlpacaStreamClient;
|
|
50792
50849
|
} (websockets));
|
|
50793
50850
|
return websockets;
|
|
50794
50851
|
}
|
|
@@ -51040,13 +51097,13 @@ var hasRequiredDist;
|
|
|
51040
51097
|
function requireDist () {
|
|
51041
51098
|
if (hasRequiredDist) return dist$1.exports;
|
|
51042
51099
|
hasRequiredDist = 1;
|
|
51043
|
-
(function (module, exports) {
|
|
51100
|
+
(function (module, exports$1) {
|
|
51044
51101
|
var __importDefault = (dist && dist.__importDefault) || function (mod) {
|
|
51045
51102
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51046
51103
|
};
|
|
51047
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51104
|
+
Object.defineProperty(exports$1, "__esModule", { value: true });
|
|
51048
51105
|
const alpaca_trade_api_1 = __importDefault(requireAlpacaTradeApi());
|
|
51049
|
-
exports.default = alpaca_trade_api_1.default;
|
|
51106
|
+
exports$1.default = alpaca_trade_api_1.default;
|
|
51050
51107
|
module.exports = alpaca_trade_api_1.default;
|
|
51051
51108
|
} (dist$1, dist$1.exports));
|
|
51052
51109
|
return dist$1.exports;
|