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