@doclo/core 0.1.10 → 0.1.11
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.d.ts +161 -1
- package/dist/index.js +189 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -928,4 +928,164 @@ declare function getAllModels(): ResolvedModelMetadata[];
|
|
|
928
928
|
*/
|
|
929
929
|
declare function clearModelRegistry(): void;
|
|
930
930
|
|
|
931
|
-
|
|
931
|
+
/**
|
|
932
|
+
* @doclo/core - Retry Utilities
|
|
933
|
+
*
|
|
934
|
+
* Shared retry infrastructure for LLM and OCR providers.
|
|
935
|
+
* Includes exponential backoff, circuit breaker pattern, and error classification.
|
|
936
|
+
*/
|
|
937
|
+
/**
|
|
938
|
+
* Configuration for retry behavior
|
|
939
|
+
*/
|
|
940
|
+
interface RetryConfig {
|
|
941
|
+
/** Maximum number of retry attempts (default: 2) */
|
|
942
|
+
maxRetries?: number;
|
|
943
|
+
/** Base delay in milliseconds between retries (default: 1000) */
|
|
944
|
+
retryDelay?: number;
|
|
945
|
+
/** Enable exponential backoff (default: true) */
|
|
946
|
+
useExponentialBackoff?: boolean;
|
|
947
|
+
/** Maximum delay cap in milliseconds (default: 30000) */
|
|
948
|
+
maxDelay?: number;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Configuration for circuit breaker behavior
|
|
952
|
+
*/
|
|
953
|
+
interface CircuitBreakerConfig {
|
|
954
|
+
/** Number of consecutive failures before opening circuit (default: 3) */
|
|
955
|
+
threshold?: number;
|
|
956
|
+
/** Time in milliseconds before trying again after circuit opens (default: 30000) */
|
|
957
|
+
resetTimeout?: number;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Internal state for a circuit breaker
|
|
961
|
+
*/
|
|
962
|
+
interface CircuitBreakerState {
|
|
963
|
+
consecutiveFailures: number;
|
|
964
|
+
lastFailureTime?: number;
|
|
965
|
+
isOpen: boolean;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Circuit breaker interface
|
|
969
|
+
*/
|
|
970
|
+
interface CircuitBreaker {
|
|
971
|
+
/** Check if circuit is currently open (should skip requests) */
|
|
972
|
+
isOpen(): boolean;
|
|
973
|
+
/** Record a successful request (resets failure count) */
|
|
974
|
+
recordSuccess(): void;
|
|
975
|
+
/** Record a failed request (may open circuit) */
|
|
976
|
+
recordFailure(): void;
|
|
977
|
+
/** Get current state for inspection */
|
|
978
|
+
getState(): CircuitBreakerState;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Options for the withRetry wrapper
|
|
982
|
+
*/
|
|
983
|
+
interface WithRetryOptions<T> extends RetryConfig {
|
|
984
|
+
/** Called before each retry attempt (for logging/observability) */
|
|
985
|
+
onRetry?: (attempt: number, error: Error, delay: number) => void | Promise<void>;
|
|
986
|
+
/** Override to parse Retry-After header from response errors */
|
|
987
|
+
getRetryAfter?: (error: Error) => number | undefined;
|
|
988
|
+
/** Circuit breaker to use (optional) */
|
|
989
|
+
circuitBreaker?: CircuitBreaker;
|
|
990
|
+
}
|
|
991
|
+
/** Default retry configuration */
|
|
992
|
+
declare const DEFAULT_RETRY_CONFIG: Required<RetryConfig>;
|
|
993
|
+
/** Default circuit breaker configuration */
|
|
994
|
+
declare const DEFAULT_CIRCUIT_BREAKER_CONFIG: Required<CircuitBreakerConfig>;
|
|
995
|
+
/**
|
|
996
|
+
* Determines if an error is retryable based on its message content.
|
|
997
|
+
*
|
|
998
|
+
* Retryable errors include:
|
|
999
|
+
* - HTTP 408, 429, 500, 502, 503, 504
|
|
1000
|
+
* - Timeout errors
|
|
1001
|
+
* - Rate limit errors
|
|
1002
|
+
* - Network errors (ECONNRESET, ETIMEDOUT, etc.)
|
|
1003
|
+
*
|
|
1004
|
+
* Non-retryable errors include:
|
|
1005
|
+
* - HTTP 400, 401, 403, 404 (client errors)
|
|
1006
|
+
* - Business logic failures
|
|
1007
|
+
*
|
|
1008
|
+
* @param error - The error to classify
|
|
1009
|
+
* @returns true if the error is retryable
|
|
1010
|
+
*/
|
|
1011
|
+
declare function isRetryableError(error: Error): boolean;
|
|
1012
|
+
/**
|
|
1013
|
+
* Extracts HTTP status code from an error message if present.
|
|
1014
|
+
*
|
|
1015
|
+
* @param error - The error to extract status from
|
|
1016
|
+
* @returns The HTTP status code or undefined
|
|
1017
|
+
*/
|
|
1018
|
+
declare function extractStatusCode(error: Error): number | undefined;
|
|
1019
|
+
/**
|
|
1020
|
+
* Parses Retry-After header value from error message or response.
|
|
1021
|
+
* Supports both seconds (integer) and HTTP-date formats.
|
|
1022
|
+
*
|
|
1023
|
+
* @param error - Error that may contain Retry-After information
|
|
1024
|
+
* @returns Delay in milliseconds, or undefined if not found
|
|
1025
|
+
*/
|
|
1026
|
+
declare function parseRetryAfter(error: Error): number | undefined;
|
|
1027
|
+
/**
|
|
1028
|
+
* Calculates the delay before the next retry attempt.
|
|
1029
|
+
*
|
|
1030
|
+
* With exponential backoff enabled (default):
|
|
1031
|
+
* - Attempt 1: baseDelay * 2^0 = 1x baseDelay
|
|
1032
|
+
* - Attempt 2: baseDelay * 2^1 = 2x baseDelay
|
|
1033
|
+
* - Attempt 3: baseDelay * 2^2 = 4x baseDelay
|
|
1034
|
+
* Plus random jitter (0-1000ms) to prevent thundering herd.
|
|
1035
|
+
*
|
|
1036
|
+
* @param attempt - Current attempt number (1-indexed)
|
|
1037
|
+
* @param config - Retry configuration
|
|
1038
|
+
* @returns Delay in milliseconds
|
|
1039
|
+
*/
|
|
1040
|
+
declare function calculateRetryDelay(attempt: number, config?: RetryConfig): number;
|
|
1041
|
+
/**
|
|
1042
|
+
* Creates or retrieves a circuit breaker for a given key.
|
|
1043
|
+
*
|
|
1044
|
+
* Circuit breakers prevent cascading failures by:
|
|
1045
|
+
* 1. Tracking consecutive failures per provider/endpoint
|
|
1046
|
+
* 2. "Opening" the circuit after threshold failures (skipping requests)
|
|
1047
|
+
* 3. Allowing a retry after resetTimeout (half-open state)
|
|
1048
|
+
* 4. Closing the circuit on success
|
|
1049
|
+
*
|
|
1050
|
+
* @param key - Unique identifier (e.g., "datalab:surya" or "openai:gpt-4")
|
|
1051
|
+
* @param config - Circuit breaker configuration
|
|
1052
|
+
* @returns CircuitBreaker instance
|
|
1053
|
+
*/
|
|
1054
|
+
declare function createCircuitBreaker(key: string, config?: CircuitBreakerConfig): CircuitBreaker;
|
|
1055
|
+
/**
|
|
1056
|
+
* Clears all circuit breakers. Useful for testing.
|
|
1057
|
+
*/
|
|
1058
|
+
declare function clearCircuitBreakers(): void;
|
|
1059
|
+
/**
|
|
1060
|
+
* Gets the circuit breaker for a key without creating one.
|
|
1061
|
+
*
|
|
1062
|
+
* @param key - Unique identifier
|
|
1063
|
+
* @returns CircuitBreaker or undefined if not found
|
|
1064
|
+
*/
|
|
1065
|
+
declare function getCircuitBreaker(key: string): CircuitBreaker | undefined;
|
|
1066
|
+
/**
|
|
1067
|
+
* Wraps an async function with retry logic.
|
|
1068
|
+
*
|
|
1069
|
+
* @example
|
|
1070
|
+
* ```typescript
|
|
1071
|
+
* const result = await withRetry(
|
|
1072
|
+
* () => fetchWithTimeout(url, options),
|
|
1073
|
+
* {
|
|
1074
|
+
* maxRetries: 3,
|
|
1075
|
+
* retryDelay: 1000,
|
|
1076
|
+
* useExponentialBackoff: true,
|
|
1077
|
+
* onRetry: (attempt, error, delay) => {
|
|
1078
|
+
* console.log(`Retry ${attempt} after ${delay}ms: ${error.message}`);
|
|
1079
|
+
* }
|
|
1080
|
+
* }
|
|
1081
|
+
* );
|
|
1082
|
+
* ```
|
|
1083
|
+
*
|
|
1084
|
+
* @param fn - Async function to retry
|
|
1085
|
+
* @param options - Retry options
|
|
1086
|
+
* @returns Result of the function
|
|
1087
|
+
* @throws Last error if all retries fail
|
|
1088
|
+
*/
|
|
1089
|
+
declare function withRetry<T>(fn: () => Promise<T>, options?: WithRetryOptions<T>): Promise<T>;
|
|
1090
|
+
|
|
1091
|
+
export { type AcceptedMimeType, AccessMethod, type AllAutoVariables, type AutoVariablesForNode, type BaseProviderConfig, type CategorizeAutoVariables, type CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerState, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_RETRY_CONFIG, type DocumentMimeType, type ExtractAutoVariables, type FeatureName, FlowInputValidationError, type InputRequirements, type ModelMetadata, type ModelQueryFilter, type NormalizedCapabilities, type NormalizedFeatures, type NormalizedProviderMetadata, type OCRProviderConfig, type OutputFormatSupport, type ParseAutoVariables, type PromptVariables, type ProviderConfig, type ProviderInputType, type ProviderInstance, type ProviderMetadataWithModels, type ProviderQueryFilter, type ProviderRegistry, type ProviderSecrets, ProviderVendor, type ResolvedModelMetadata, type RetryConfig, type VLMProviderConfig, type WithRetryOptions, bufferToBase64, bufferToDataUri, buildProviderFromConfig, buildProvidersFromConfigs, calculateRetryDelay, clearCircuitBreakers, clearModelRegistry, clearProviderRegistry, createCircuitBreaker, defineMarkerProvider, defineSuryaProvider, defineVLMProvider, detectDocumentType, detectMimeTypeFromBase64, detectMimeTypeFromBase64Async, detectMimeTypeFromBytes, extractBase64, extractStatusCode, getAllModels, getAllProviders, getCheapestProviderFor, getCircuitBreaker, getModelsForNode, getProviderById, getProvidersBySource, getProvidersForLargeFiles, getProvidersForMimeType, isPDFDocument, isRetryableError, parseRetryAfter, queryModels, queryProviders, registerProviderMetadata, registerProviderWithModels, resolveDocument, resolveModelMetadata, validateFlowInputFormat, validateMimeType, validateMimeTypeAsync, withRetry };
|
package/dist/index.js
CHANGED
|
@@ -2288,7 +2288,187 @@ function getAllModels() {
|
|
|
2288
2288
|
function clearModelRegistry() {
|
|
2289
2289
|
modelRegistry.clear();
|
|
2290
2290
|
}
|
|
2291
|
+
|
|
2292
|
+
// src/retry.ts
|
|
2293
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
2294
|
+
maxRetries: 2,
|
|
2295
|
+
retryDelay: 1e3,
|
|
2296
|
+
useExponentialBackoff: true,
|
|
2297
|
+
maxDelay: 3e4
|
|
2298
|
+
};
|
|
2299
|
+
var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
|
|
2300
|
+
threshold: 3,
|
|
2301
|
+
resetTimeout: 3e4
|
|
2302
|
+
};
|
|
2303
|
+
var RETRYABLE_STATUS_CODES = ["408", "429", "500", "502", "503", "504"];
|
|
2304
|
+
var RETRYABLE_ERROR_PATTERNS = [
|
|
2305
|
+
"timeout",
|
|
2306
|
+
"rate limit",
|
|
2307
|
+
"overloaded",
|
|
2308
|
+
"econnreset",
|
|
2309
|
+
"etimedout",
|
|
2310
|
+
"enotfound",
|
|
2311
|
+
"econnrefused",
|
|
2312
|
+
"socket hang up",
|
|
2313
|
+
"network error"
|
|
2314
|
+
];
|
|
2315
|
+
function isRetryableError(error) {
|
|
2316
|
+
const message = error.message.toLowerCase();
|
|
2317
|
+
for (const code of RETRYABLE_STATUS_CODES) {
|
|
2318
|
+
if (message.includes(code)) {
|
|
2319
|
+
return true;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
for (const pattern of RETRYABLE_ERROR_PATTERNS) {
|
|
2323
|
+
if (message.includes(pattern)) {
|
|
2324
|
+
return true;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
return false;
|
|
2328
|
+
}
|
|
2329
|
+
function extractStatusCode(error) {
|
|
2330
|
+
const patterns = [
|
|
2331
|
+
/\b(\d{3})\b/,
|
|
2332
|
+
// Just the status code
|
|
2333
|
+
/status[:\s]+(\d{3})/i,
|
|
2334
|
+
/http[:\s]+(\d{3})/i,
|
|
2335
|
+
/failed[:\s]+(\d{3})/i
|
|
2336
|
+
];
|
|
2337
|
+
for (const pattern of patterns) {
|
|
2338
|
+
const match = error.message.match(pattern);
|
|
2339
|
+
if (match && match[1]) {
|
|
2340
|
+
const code = parseInt(match[1], 10);
|
|
2341
|
+
if (code >= 100 && code < 600) {
|
|
2342
|
+
return code;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
return void 0;
|
|
2347
|
+
}
|
|
2348
|
+
function parseRetryAfter(error) {
|
|
2349
|
+
const message = error.message;
|
|
2350
|
+
const match = message.match(/retry-after[:\s]+(\d+)/i);
|
|
2351
|
+
if (match && match[1]) {
|
|
2352
|
+
const seconds = parseInt(match[1], 10);
|
|
2353
|
+
if (!isNaN(seconds) && seconds > 0 && seconds < 3600) {
|
|
2354
|
+
return seconds * 1e3;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
return void 0;
|
|
2358
|
+
}
|
|
2359
|
+
function calculateRetryDelay(attempt, config = {}) {
|
|
2360
|
+
const {
|
|
2361
|
+
retryDelay = DEFAULT_RETRY_CONFIG.retryDelay,
|
|
2362
|
+
useExponentialBackoff = DEFAULT_RETRY_CONFIG.useExponentialBackoff,
|
|
2363
|
+
maxDelay = DEFAULT_RETRY_CONFIG.maxDelay
|
|
2364
|
+
} = config;
|
|
2365
|
+
if (!useExponentialBackoff) {
|
|
2366
|
+
return retryDelay;
|
|
2367
|
+
}
|
|
2368
|
+
const exponentialDelay = retryDelay * Math.pow(2, attempt - 1);
|
|
2369
|
+
const jitter = Math.random() * 1e3;
|
|
2370
|
+
return Math.min(exponentialDelay + jitter, maxDelay);
|
|
2371
|
+
}
|
|
2372
|
+
var circuitBreakerRegistry = /* @__PURE__ */ new Map();
|
|
2373
|
+
function createCircuitBreaker(key, config = {}) {
|
|
2374
|
+
const existing = circuitBreakerRegistry.get(key);
|
|
2375
|
+
if (existing) {
|
|
2376
|
+
return existing;
|
|
2377
|
+
}
|
|
2378
|
+
const {
|
|
2379
|
+
threshold = DEFAULT_CIRCUIT_BREAKER_CONFIG.threshold,
|
|
2380
|
+
resetTimeout = DEFAULT_CIRCUIT_BREAKER_CONFIG.resetTimeout
|
|
2381
|
+
} = config;
|
|
2382
|
+
let state = {
|
|
2383
|
+
consecutiveFailures: 0,
|
|
2384
|
+
isOpen: false
|
|
2385
|
+
};
|
|
2386
|
+
const circuitBreaker = {
|
|
2387
|
+
isOpen() {
|
|
2388
|
+
if (!state.isOpen) return false;
|
|
2389
|
+
if (state.lastFailureTime && Date.now() - state.lastFailureTime > resetTimeout) {
|
|
2390
|
+
state = {
|
|
2391
|
+
consecutiveFailures: 0,
|
|
2392
|
+
isOpen: false
|
|
2393
|
+
};
|
|
2394
|
+
return false;
|
|
2395
|
+
}
|
|
2396
|
+
return true;
|
|
2397
|
+
},
|
|
2398
|
+
recordSuccess() {
|
|
2399
|
+
state = {
|
|
2400
|
+
consecutiveFailures: 0,
|
|
2401
|
+
isOpen: false
|
|
2402
|
+
};
|
|
2403
|
+
},
|
|
2404
|
+
recordFailure() {
|
|
2405
|
+
state.consecutiveFailures++;
|
|
2406
|
+
state.lastFailureTime = Date.now();
|
|
2407
|
+
if (state.consecutiveFailures >= threshold) {
|
|
2408
|
+
state.isOpen = true;
|
|
2409
|
+
console.warn(`Circuit breaker opened for ${key} after ${state.consecutiveFailures} consecutive failures`);
|
|
2410
|
+
}
|
|
2411
|
+
},
|
|
2412
|
+
getState() {
|
|
2413
|
+
return { ...state };
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
circuitBreakerRegistry.set(key, circuitBreaker);
|
|
2417
|
+
return circuitBreaker;
|
|
2418
|
+
}
|
|
2419
|
+
function clearCircuitBreakers() {
|
|
2420
|
+
circuitBreakerRegistry.clear();
|
|
2421
|
+
}
|
|
2422
|
+
function getCircuitBreaker(key) {
|
|
2423
|
+
return circuitBreakerRegistry.get(key);
|
|
2424
|
+
}
|
|
2425
|
+
async function withRetry(fn, options = {}) {
|
|
2426
|
+
const {
|
|
2427
|
+
maxRetries = DEFAULT_RETRY_CONFIG.maxRetries,
|
|
2428
|
+
retryDelay = DEFAULT_RETRY_CONFIG.retryDelay,
|
|
2429
|
+
useExponentialBackoff = DEFAULT_RETRY_CONFIG.useExponentialBackoff,
|
|
2430
|
+
maxDelay = DEFAULT_RETRY_CONFIG.maxDelay,
|
|
2431
|
+
onRetry,
|
|
2432
|
+
getRetryAfter,
|
|
2433
|
+
circuitBreaker
|
|
2434
|
+
} = options;
|
|
2435
|
+
if (circuitBreaker?.isOpen()) {
|
|
2436
|
+
throw new Error("Circuit breaker is open");
|
|
2437
|
+
}
|
|
2438
|
+
let lastError = null;
|
|
2439
|
+
const totalAttempts = maxRetries + 1;
|
|
2440
|
+
for (let attempt = 1; attempt <= totalAttempts; attempt++) {
|
|
2441
|
+
try {
|
|
2442
|
+
const result = await fn();
|
|
2443
|
+
circuitBreaker?.recordSuccess();
|
|
2444
|
+
return result;
|
|
2445
|
+
} catch (error) {
|
|
2446
|
+
lastError = error;
|
|
2447
|
+
const isLastAttempt = attempt === totalAttempts;
|
|
2448
|
+
const canRetry = !isLastAttempt && isRetryableError(lastError);
|
|
2449
|
+
if (!canRetry) {
|
|
2450
|
+
break;
|
|
2451
|
+
}
|
|
2452
|
+
let delay = calculateRetryDelay(attempt, { retryDelay, useExponentialBackoff, maxDelay });
|
|
2453
|
+
const retryAfter = getRetryAfter?.(lastError) ?? parseRetryAfter(lastError);
|
|
2454
|
+
if (retryAfter !== void 0 && retryAfter > 0) {
|
|
2455
|
+
delay = Math.min(retryAfter, maxDelay);
|
|
2456
|
+
}
|
|
2457
|
+
if (onRetry) {
|
|
2458
|
+
await onRetry(attempt, lastError, delay);
|
|
2459
|
+
}
|
|
2460
|
+
await sleep(delay);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
circuitBreaker?.recordFailure();
|
|
2464
|
+
throw lastError;
|
|
2465
|
+
}
|
|
2466
|
+
function sleep(ms) {
|
|
2467
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2468
|
+
}
|
|
2291
2469
|
export {
|
|
2470
|
+
DEFAULT_CIRCUIT_BREAKER_CONFIG,
|
|
2471
|
+
DEFAULT_RETRY_CONFIG,
|
|
2292
2472
|
FlowExecutionError,
|
|
2293
2473
|
FlowInputValidationError,
|
|
2294
2474
|
FlowValidationError,
|
|
@@ -2299,9 +2479,12 @@ export {
|
|
|
2299
2479
|
bufferToDataUri,
|
|
2300
2480
|
buildProviderFromConfig,
|
|
2301
2481
|
buildProvidersFromConfigs,
|
|
2482
|
+
calculateRetryDelay,
|
|
2302
2483
|
canStartForEachItemFlow,
|
|
2484
|
+
clearCircuitBreakers,
|
|
2303
2485
|
clearModelRegistry,
|
|
2304
2486
|
clearProviderRegistry,
|
|
2487
|
+
createCircuitBreaker,
|
|
2305
2488
|
createIdentity,
|
|
2306
2489
|
defineMarkerProvider,
|
|
2307
2490
|
defineSuryaProvider,
|
|
@@ -2312,9 +2495,11 @@ export {
|
|
|
2312
2495
|
detectMimeTypeFromBytes,
|
|
2313
2496
|
extractBase64,
|
|
2314
2497
|
extractErrorMessage,
|
|
2498
|
+
extractStatusCode,
|
|
2315
2499
|
getAllModels,
|
|
2316
2500
|
getAllProviders,
|
|
2317
2501
|
getCheapestProviderFor,
|
|
2502
|
+
getCircuitBreaker,
|
|
2318
2503
|
getCompatibleTargets,
|
|
2319
2504
|
getDocumentPageCount,
|
|
2320
2505
|
getModelsForNode,
|
|
@@ -2331,8 +2516,10 @@ export {
|
|
|
2331
2516
|
getValidForEachStarters,
|
|
2332
2517
|
isLocalEndpoint,
|
|
2333
2518
|
isPDFDocument,
|
|
2519
|
+
isRetryableError,
|
|
2334
2520
|
node,
|
|
2335
2521
|
parseProviderString,
|
|
2522
|
+
parseRetryAfter,
|
|
2336
2523
|
protectReservedVariables,
|
|
2337
2524
|
queryModels,
|
|
2338
2525
|
queryProviders,
|
|
@@ -2347,6 +2534,7 @@ export {
|
|
|
2347
2534
|
validateJson,
|
|
2348
2535
|
validateMimeType,
|
|
2349
2536
|
validateMimeTypeAsync,
|
|
2350
|
-
validateNodeConnection
|
|
2537
|
+
validateNodeConnection,
|
|
2538
|
+
withRetry
|
|
2351
2539
|
};
|
|
2352
2540
|
//# sourceMappingURL=index.js.map
|