@karpeleslab/klbfw 0.2.25 → 0.2.27
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/index.d.ts +25 -5
- package/package.json +1 -1
- package/upload.js +76 -12
package/index.d.ts
CHANGED
|
@@ -61,6 +61,25 @@ interface RestResponse<T = any> {
|
|
|
61
61
|
[key: string]: any;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Context object for REST API calls.
|
|
66
|
+
* Keys are single characters representing different context dimensions.
|
|
67
|
+
*/
|
|
68
|
+
interface Context {
|
|
69
|
+
/** Branch identifier */
|
|
70
|
+
b?: string;
|
|
71
|
+
/** Currency code (e.g., 'USD', 'EUR') */
|
|
72
|
+
c?: string;
|
|
73
|
+
/** Group identifier */
|
|
74
|
+
g?: string;
|
|
75
|
+
/** Language/locale code (e.g., 'en-US', 'ja-JP') */
|
|
76
|
+
l?: string;
|
|
77
|
+
/** Timezone identifier (e.g., 'Asia/Tokyo', 'America/New_York') */
|
|
78
|
+
t?: string;
|
|
79
|
+
/** User identifier */
|
|
80
|
+
u?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
64
83
|
/** REST API error object (thrown on promise rejection) */
|
|
65
84
|
interface RestError {
|
|
66
85
|
/** Always 'error' for error responses */
|
|
@@ -172,7 +191,7 @@ interface Price extends PriceValue {
|
|
|
172
191
|
tax_rate?: number;
|
|
173
192
|
}
|
|
174
193
|
|
|
175
|
-
declare function rest<T = any>(name: string, verb: string, params?: Record<string, any>, context?:
|
|
194
|
+
declare function rest<T = any>(name: string, verb: string, params?: Record<string, any>, context?: Context): Promise<RestResponse<T>>;
|
|
176
195
|
declare function rest_get<T = any>(name: string, params?: Record<string, any>): Promise<RestResponse<T>>; // Backward compatibility
|
|
177
196
|
declare function restGet<T = any>(name: string, params?: Record<string, any>): Promise<RestResponse<T>>;
|
|
178
197
|
|
|
@@ -218,7 +237,7 @@ interface SSESource {
|
|
|
218
237
|
close(): void;
|
|
219
238
|
}
|
|
220
239
|
|
|
221
|
-
declare function restSSE(name: string, method?: string, params?: Record<string, any>, context?:
|
|
240
|
+
declare function restSSE(name: string, method?: string, params?: Record<string, any>, context?: Context): SSESource;
|
|
222
241
|
|
|
223
242
|
// Upload module types
|
|
224
243
|
|
|
@@ -266,7 +285,7 @@ interface UploadLegacyOptions {
|
|
|
266
285
|
/** @deprecated Use uploadFile() instead */
|
|
267
286
|
declare const upload: {
|
|
268
287
|
init(path: string, params?: Record<string, any>, notify?: (status: any) => void): Promise<any> | ((files: any) => Promise<any>);
|
|
269
|
-
append(path: string, file: File | object, params?: Record<string, any>, context?:
|
|
288
|
+
append(path: string, file: File | object, params?: Record<string, any>, context?: Context): Promise<any>;
|
|
270
289
|
run(): void;
|
|
271
290
|
getStatus(): { queue: any[]; running: any[]; failed: any[] };
|
|
272
291
|
resume(): void;
|
|
@@ -284,7 +303,7 @@ declare function uploadFile(
|
|
|
284
303
|
buffer: UploadFileInput,
|
|
285
304
|
method?: string,
|
|
286
305
|
params?: Record<string, any>,
|
|
287
|
-
context?:
|
|
306
|
+
context?: Context,
|
|
288
307
|
options?: UploadFileOptions
|
|
289
308
|
): Promise<any>;
|
|
290
309
|
|
|
@@ -294,7 +313,7 @@ declare function uploadManyFiles(
|
|
|
294
313
|
files: UploadFileInput[],
|
|
295
314
|
method?: string,
|
|
296
315
|
params?: Record<string, any>,
|
|
297
|
-
context?:
|
|
316
|
+
context?: Context,
|
|
298
317
|
options?: UploadManyFilesOptions
|
|
299
318
|
): Promise<any[]>;
|
|
300
319
|
|
|
@@ -334,6 +353,7 @@ export {
|
|
|
334
353
|
uploadManyFiles,
|
|
335
354
|
getI18N,
|
|
336
355
|
trimPrefix,
|
|
356
|
+
Context,
|
|
337
357
|
RestPaging,
|
|
338
358
|
RestResponse,
|
|
339
359
|
RestError,
|
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;
|