@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/upload.js +76 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karpeleslab/klbfw",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
4
4
  "description": "Frontend Framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
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). Can return a Promise
38
- * that, if resolved, will cause the failed operation to be retried. Context contains
39
- * { phase, blockNum, attempt } for block uploads or { phase, attempt } for other operations.
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 and error handling
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
- * console.log(`Error in ${ctx.phase}, attempt ${ctx.attempt}:`, error.message);
59
- * if (ctx.attempt < 3) {
60
- * await new Promise(r => setTimeout(r, 1000)); // Wait 1s before retry
61
- * return; // Resolve to trigger retry
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;