@karpeleslab/klbfw 0.2.25 → 0.2.26
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/package.json +1 -1
- package/upload.js +76 -12
package/package.json
CHANGED
package/upload.js
CHANGED
|
@@ -13,6 +13,17 @@ const rest = require('./rest');
|
|
|
13
13
|
const fwWrapper = require('./fw-wrapper');
|
|
14
14
|
const { env, utils, awsReq, readChunkFromStream, readFileSlice } = require('./upload-internal');
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Sleep for a specified duration with exponential backoff and jitter
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
function retryDelay(attempt, maxRetries) {
|
|
21
|
+
// Exponential backoff: 1s, 2s, 4s (capped at 4s) plus random jitter (0-500ms)
|
|
22
|
+
const baseDelay = Math.min(1000 * Math.pow(2, attempt - 1), 4000);
|
|
23
|
+
const jitter = Math.random() * 500;
|
|
24
|
+
return new Promise(resolve => setTimeout(resolve, baseDelay + jitter));
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
/**
|
|
17
28
|
* Simple file upload function
|
|
18
29
|
*
|
|
@@ -34,9 +45,10 @@ const { env, utils, awsReq, readChunkFromStream, readFileSlice } = require('./up
|
|
|
34
45
|
* @param {Object} [context=null] - Request context (uses default context if not provided)
|
|
35
46
|
* @param {Object} [options={}] - Upload options
|
|
36
47
|
* @param {Function} [options.onProgress] - Progress callback(progress) where progress is 0-1
|
|
37
|
-
* @param {Function} [options.onError] - Error callback(error, context).
|
|
38
|
-
* that, if resolved, will
|
|
39
|
-
*
|
|
48
|
+
* @param {Function} [options.onError] - Error callback(error, context). Called only after 3
|
|
49
|
+
* automatic retries have failed. Can return a Promise that, if resolved, will reset the
|
|
50
|
+
* retry counter and attempt 3 more automatic retries. Context contains { phase, blockNum,
|
|
51
|
+
* attempt } for block uploads or { phase, attempt } for other operations.
|
|
40
52
|
* @param {AbortSignal} [options.signal] - AbortSignal for cancellation. Use AbortController to cancel.
|
|
41
53
|
* @returns {Promise<Object>} - Resolves with the full REST response. Rejects with AbortError if cancelled.
|
|
42
54
|
*
|
|
@@ -49,18 +61,16 @@ const { env, utils, awsReq, readChunkFromStream, readFileSlice } = require('./up
|
|
|
49
61
|
* });
|
|
50
62
|
*
|
|
51
63
|
* @example
|
|
52
|
-
* // Upload with progress
|
|
64
|
+
* // Upload with progress - transient failures are automatically retried up to 3 times
|
|
53
65
|
* const result = await uploadFile('Misc/Debug:testUpload', buffer, 'POST', {
|
|
54
66
|
* filename: 'large-file.bin'
|
|
55
67
|
* }, null, {
|
|
56
68
|
* onProgress: (progress) => console.log(`${Math.round(progress * 100)}%`),
|
|
57
69
|
* onError: async (error, ctx) => {
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* }
|
|
63
|
-
* throw error; // Give up after 3 attempts
|
|
70
|
+
* // Called only after 3 automatic retries have failed
|
|
71
|
+
* console.log(`Error in ${ctx.phase} after ${ctx.attempt} attempts:`, error.message);
|
|
72
|
+
* // Resolve to reset counter and try 3 more times, or throw to give up
|
|
73
|
+
* throw error;
|
|
64
74
|
* }
|
|
65
75
|
* });
|
|
66
76
|
*
|
|
@@ -338,9 +348,15 @@ async function doPutUpload(file, uploadInfo, context, options) {
|
|
|
338
348
|
} catch (error) {
|
|
339
349
|
// Check if aborted during completion
|
|
340
350
|
checkAbort();
|
|
351
|
+
// Auto-retry up to 3 times before triggering onError
|
|
352
|
+
if (attempt < 3) {
|
|
353
|
+
await retryDelay(attempt);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
341
356
|
if (onError) {
|
|
342
357
|
await onError(error, { phase: 'complete', attempt });
|
|
343
|
-
// If onError resolves, retry
|
|
358
|
+
// If onError resolves, reset attempt counter and retry
|
|
359
|
+
attempt = 0;
|
|
344
360
|
continue;
|
|
345
361
|
}
|
|
346
362
|
throw error;
|
|
@@ -388,8 +404,15 @@ async function uploadPutBlockWithDataAndRetry(uploadInfo, blockNum, startByte, d
|
|
|
388
404
|
if (error.name === 'AbortError') {
|
|
389
405
|
throw error;
|
|
390
406
|
}
|
|
407
|
+
// Auto-retry up to 3 times before triggering onError
|
|
408
|
+
if (attempt < 3) {
|
|
409
|
+
await retryDelay(attempt);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
391
412
|
if (onError) {
|
|
392
413
|
await onError(error, { phase: 'upload', blockNum, attempt });
|
|
414
|
+
// If onError resolves, reset attempt counter and retry
|
|
415
|
+
attempt = 0;
|
|
393
416
|
continue;
|
|
394
417
|
}
|
|
395
418
|
throw error;
|
|
@@ -412,9 +435,15 @@ async function uploadPutBlockWithRetry(file, uploadInfo, blockNum, blockSize, on
|
|
|
412
435
|
if (error.name === 'AbortError') {
|
|
413
436
|
throw error;
|
|
414
437
|
}
|
|
438
|
+
// Auto-retry up to 3 times before triggering onError
|
|
439
|
+
if (attempt < 3) {
|
|
440
|
+
await retryDelay(attempt);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
415
443
|
if (onError) {
|
|
416
444
|
await onError(error, { phase: 'upload', blockNum, attempt });
|
|
417
|
-
// If onError resolves, retry
|
|
445
|
+
// If onError resolves, reset attempt counter and retry
|
|
446
|
+
attempt = 0;
|
|
418
447
|
continue;
|
|
419
448
|
}
|
|
420
449
|
throw error;
|
|
@@ -526,8 +555,15 @@ async function doAwsUpload(file, uploadInfo, context, options) {
|
|
|
526
555
|
if (error.name === 'AbortError') {
|
|
527
556
|
throw error;
|
|
528
557
|
}
|
|
558
|
+
// Auto-retry up to 3 times before triggering onError
|
|
559
|
+
if (initAttempt < 3) {
|
|
560
|
+
await retryDelay(initAttempt);
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
529
563
|
if (onError) {
|
|
530
564
|
await onError(error, { phase: 'init', attempt: initAttempt });
|
|
565
|
+
// If onError resolves, reset attempt counter and retry
|
|
566
|
+
initAttempt = 0;
|
|
531
567
|
continue;
|
|
532
568
|
}
|
|
533
569
|
throw error;
|
|
@@ -636,8 +672,15 @@ async function doAwsUpload(file, uploadInfo, context, options) {
|
|
|
636
672
|
await abortMultipartUpload(uploadId);
|
|
637
673
|
throw error;
|
|
638
674
|
}
|
|
675
|
+
// Auto-retry up to 3 times before triggering onError
|
|
676
|
+
if (completeAttempt < 3) {
|
|
677
|
+
await retryDelay(completeAttempt);
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
639
680
|
if (onError) {
|
|
640
681
|
await onError(error, { phase: 'complete', attempt: completeAttempt });
|
|
682
|
+
// If onError resolves, reset attempt counter and retry
|
|
683
|
+
completeAttempt = 0;
|
|
641
684
|
continue;
|
|
642
685
|
}
|
|
643
686
|
throw error;
|
|
@@ -662,8 +705,15 @@ async function doAwsUpload(file, uploadInfo, context, options) {
|
|
|
662
705
|
} catch (error) {
|
|
663
706
|
// Check if aborted during completion
|
|
664
707
|
checkAbort();
|
|
708
|
+
// Auto-retry up to 3 times before triggering onError
|
|
709
|
+
if (handleAttempt < 3) {
|
|
710
|
+
await retryDelay(handleAttempt);
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
665
713
|
if (onError) {
|
|
666
714
|
await onError(error, { phase: 'handleComplete', attempt: handleAttempt });
|
|
715
|
+
// If onError resolves, reset attempt counter and retry
|
|
716
|
+
handleAttempt = 0;
|
|
667
717
|
continue;
|
|
668
718
|
}
|
|
669
719
|
throw error;
|
|
@@ -703,8 +753,15 @@ async function uploadAwsBlockWithDataAndRetry(uploadInfo, uploadId, blockNum, da
|
|
|
703
753
|
if (error.name === 'AbortError') {
|
|
704
754
|
throw error;
|
|
705
755
|
}
|
|
756
|
+
// Auto-retry up to 3 times before triggering onError
|
|
757
|
+
if (attempt < 3) {
|
|
758
|
+
await retryDelay(attempt);
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
706
761
|
if (onError) {
|
|
707
762
|
await onError(error, { phase: 'upload', blockNum, attempt });
|
|
763
|
+
// If onError resolves, reset attempt counter and retry
|
|
764
|
+
attempt = 0;
|
|
708
765
|
continue;
|
|
709
766
|
}
|
|
710
767
|
throw error;
|
|
@@ -727,8 +784,15 @@ async function uploadAwsBlockWithRetry(file, uploadInfo, uploadId, blockNum, blo
|
|
|
727
784
|
if (error.name === 'AbortError') {
|
|
728
785
|
throw error;
|
|
729
786
|
}
|
|
787
|
+
// Auto-retry up to 3 times before triggering onError
|
|
788
|
+
if (attempt < 3) {
|
|
789
|
+
await retryDelay(attempt);
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
730
792
|
if (onError) {
|
|
731
793
|
await onError(error, { phase: 'upload', blockNum, attempt });
|
|
794
|
+
// If onError resolves, reset attempt counter and retry
|
|
795
|
+
attempt = 0;
|
|
732
796
|
continue;
|
|
733
797
|
}
|
|
734
798
|
throw error;
|