@openforis/arena-core 2.4.2 → 2.4.3

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.
@@ -1,15 +1,6 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import { RetryProcessor } from './retryProcessor';
11
2
  export class FileProcessor {
12
- constructor({ file, filePath, chunkProcessor, onError, onComplete, chunkSize = FileProcessor.defaultChunkSize, maxTryings = FileProcessor.defaultMaxTryings, }) {
3
+ constructor({ file, filePath, chunkProcessor, onError, onComplete, chunkSize = FileProcessor.defaultChunkSize, maxTryings = FileProcessor.defaultMaxTryings, retryDelay = FileProcessor.defaultRetryDelay, }) {
13
4
  // State properties
14
5
  this.totalFileSize = 0;
15
6
  this.running = false;
@@ -23,6 +14,7 @@ export class FileProcessor {
23
14
  this.chunkProcessor = chunkProcessor;
24
15
  this.chunkSize = chunkSize;
25
16
  this.maxTryings = maxTryings;
17
+ this.retryDelay = retryDelay;
26
18
  this.onError = onError;
27
19
  this.onComplete = onComplete;
28
20
  if (!this.file && !this.filePath) {
@@ -55,18 +47,14 @@ export class FileProcessor {
55
47
  return Promise.resolve(file.slice((currentChunkNumber - 1) * chunkSize, currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize));
56
48
  }
57
49
  processNextChunk() {
58
- const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings } = this;
50
+ const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay } = this;
59
51
  this.processingChunk = true;
60
52
  this.extractCurrentFileChunk()
61
53
  .then((content) => {
62
54
  const retryProcessor = new RetryProcessor({
63
- processor: () => __awaiter(this, void 0, void 0, function* () {
64
- const chunkResult = yield chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content });
65
- if (this.currentChunkNumber === totalChunks && this.onComplete) {
66
- this.onComplete(chunkResult);
67
- }
68
- }),
69
- onSuccess: () => {
55
+ processor: () => chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content }),
56
+ onSuccess: (chunkResult) => {
57
+ var _a;
70
58
  this.processingChunk = false;
71
59
  if (this.currentChunkNumber < totalChunks) {
72
60
  // move to the next chunk also when paused: resume will continue from it
@@ -78,6 +66,8 @@ export class FileProcessor {
78
66
  else {
79
67
  this.running = false;
80
68
  this.completed = true;
69
+ // called outside the retried processor: if it throws, the last chunk is not processed again
70
+ (_a = this.onComplete) === null || _a === void 0 ? void 0 : _a.call(this, chunkResult);
81
71
  }
82
72
  },
83
73
  onFail: (error) => {
@@ -86,6 +76,7 @@ export class FileProcessor {
86
76
  this.running = false;
87
77
  },
88
78
  maxTryings,
79
+ retryDelay,
89
80
  });
90
81
  retryProcessor.start();
91
82
  })
@@ -130,4 +121,5 @@ export class FileProcessor {
130
121
  }
131
122
  FileProcessor.defaultChunkSize = 1024 * 1024 * 10; // 10MB
132
123
  FileProcessor.defaultMaxTryings = 5;
124
+ FileProcessor.defaultRetryDelay = 1000; // 1s, then 2s, 4s...
133
125
  //# sourceMappingURL=fileProcessor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fileProcessor.js","sourceRoot":"","sources":["../../../src/utils/fileProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { RetryProcessor } from './retryProcessor'\n\ntype ChunkProcessor = (args: {\n chunk: number\n content: Blob | string | Uint8Array\n totalChunks: number\n totalFileSize: number\n}) => Promise<any>\n\nexport type FileProcessorConstructorArgs = {\n file?: File\n filePath?: string\n chunkProcessor: ChunkProcessor\n onError?: (error: Error) => void\n onComplete?: (result: any) => void\n chunkSize?: number\n maxTryings?: number\n}\n\nexport class FileProcessor {\n static readonly defaultChunkSize = 1024 * 1024 * 10 // 10MB\n static readonly defaultMaxTryings = 5\n\n private readonly file?: File\n private readonly filePath?: string\n private readonly chunkProcessor: ChunkProcessor\n private readonly chunkSize: number\n private readonly maxTryings: number\n private readonly onError?: (error: Error) => void\n private readonly onComplete?: (result?: any) => void\n\n // State properties\n protected totalFileSize: number = 0\n protected running: boolean = false\n protected totalChunks: number = 0\n protected currentChunkNumber: number = 0\n // true while a chunk is being processed (resume must not start a second processing chain)\n protected processingChunk: boolean = false\n protected completed: boolean = false\n\n constructor({\n file,\n filePath,\n chunkProcessor,\n onError,\n onComplete,\n chunkSize = FileProcessor.defaultChunkSize,\n maxTryings = FileProcessor.defaultMaxTryings,\n }: FileProcessorConstructorArgs) {\n this.file = file\n this.filePath = filePath\n this.chunkProcessor = chunkProcessor\n this.chunkSize = chunkSize\n this.maxTryings = maxTryings\n this.onError = onError\n this.onComplete = onComplete\n\n if (!this.file && !this.filePath) {\n throw new Error('File or filePath not specified')\n }\n\n this.reset()\n }\n\n protected reset(): void {\n this.running = false\n this.totalChunks = 0\n this.currentChunkNumber = 0\n this.totalFileSize = 0\n this.processingChunk = false\n this.completed = false\n }\n\n protected calculateFileSize(): Promise<number> {\n return Promise.resolve(this.file?.size ?? 0)\n }\n\n protected onFail(error: Error): void {\n this.onError?.(error)\n }\n\n protected extractCurrentFileChunk(): Promise<Blob | string | Uint8Array> {\n const { file, currentChunkNumber, totalChunks, chunkSize } = this\n if (!file) {\n return Promise.reject(new Error('File property not initialized'))\n }\n return Promise.resolve(\n file.slice(\n (currentChunkNumber - 1) * chunkSize,\n currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize\n )\n )\n }\n\n protected processNextChunk(): void {\n const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings } = this\n\n this.processingChunk = true\n this.extractCurrentFileChunk()\n .then((content) => {\n const retryProcessor = new RetryProcessor<void>({\n processor: async () => {\n const chunkResult = await chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content })\n if (this.currentChunkNumber === totalChunks && this.onComplete) {\n this.onComplete(chunkResult)\n }\n },\n onSuccess: () => {\n this.processingChunk = false\n if (this.currentChunkNumber < totalChunks) {\n // move to the next chunk also when paused: resume will continue from it\n this.currentChunkNumber += 1\n if (this.running) {\n this.processNextChunk()\n }\n } else {\n this.running = false\n this.completed = true\n }\n },\n onFail: (error: Error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n },\n maxTryings,\n })\n retryProcessor.start()\n })\n .catch((error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n })\n }\n\n start(startFromChunk: number = 1): void {\n this.running = true\n this.completed = false\n this.currentChunkNumber = startFromChunk\n this.calculateFileSize()\n .then((fileSize) => {\n this.totalFileSize = fileSize\n this.totalChunks = Math.ceil(fileSize / this.chunkSize)\n if (this.totalChunks > 0) {\n this.processNextChunk()\n }\n })\n .catch((error) => {\n this.onFail(error)\n this.running = false\n })\n }\n\n stop(): void {\n this.reset()\n }\n\n pause(): void {\n this.running = false\n }\n\n resume(): void {\n if (this.running || this.completed) return\n this.running = true\n // if a chunk is still being processed, processing continues when it completes\n if (!this.processingChunk && this.totalChunks > 0) {\n this.processNextChunk()\n }\n }\n}\n"]}
1
+ {"version":3,"file":"fileProcessor.js","sourceRoot":"","sources":["../../../src/utils/fileProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { RetryProcessor } from './retryProcessor'\n\ntype ChunkProcessor = (args: {\n chunk: number\n content: Blob | string | Uint8Array\n totalChunks: number\n totalFileSize: number\n}) => Promise<any>\n\nexport type FileProcessorConstructorArgs = {\n file?: File\n filePath?: string\n chunkProcessor: ChunkProcessor\n onError?: (error: Error) => void\n onComplete?: (result: any) => void\n chunkSize?: number\n maxTryings?: number\n /**\n * Delay (in milliseconds) before retrying a failed chunk, doubled at every further retry.\n */\n retryDelay?: number\n}\n\nexport class FileProcessor {\n static readonly defaultChunkSize = 1024 * 1024 * 10 // 10MB\n static readonly defaultMaxTryings = 5\n static readonly defaultRetryDelay = 1000 // 1s, then 2s, 4s...\n\n private readonly file?: File\n private readonly filePath?: string\n private readonly chunkProcessor: ChunkProcessor\n private readonly chunkSize: number\n private readonly maxTryings: number\n private readonly retryDelay: number\n private readonly onError?: (error: Error) => void\n private readonly onComplete?: (result?: any) => void\n\n // State properties\n protected totalFileSize: number = 0\n protected running: boolean = false\n protected totalChunks: number = 0\n protected currentChunkNumber: number = 0\n // true while a chunk is being processed (resume must not start a second processing chain)\n protected processingChunk: boolean = false\n protected completed: boolean = false\n\n constructor({\n file,\n filePath,\n chunkProcessor,\n onError,\n onComplete,\n chunkSize = FileProcessor.defaultChunkSize,\n maxTryings = FileProcessor.defaultMaxTryings,\n retryDelay = FileProcessor.defaultRetryDelay,\n }: FileProcessorConstructorArgs) {\n this.file = file\n this.filePath = filePath\n this.chunkProcessor = chunkProcessor\n this.chunkSize = chunkSize\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n this.onError = onError\n this.onComplete = onComplete\n\n if (!this.file && !this.filePath) {\n throw new Error('File or filePath not specified')\n }\n\n this.reset()\n }\n\n protected reset(): void {\n this.running = false\n this.totalChunks = 0\n this.currentChunkNumber = 0\n this.totalFileSize = 0\n this.processingChunk = false\n this.completed = false\n }\n\n protected calculateFileSize(): Promise<number> {\n return Promise.resolve(this.file?.size ?? 0)\n }\n\n protected onFail(error: Error): void {\n this.onError?.(error)\n }\n\n protected extractCurrentFileChunk(): Promise<Blob | string | Uint8Array> {\n const { file, currentChunkNumber, totalChunks, chunkSize } = this\n if (!file) {\n return Promise.reject(new Error('File property not initialized'))\n }\n return Promise.resolve(\n file.slice(\n (currentChunkNumber - 1) * chunkSize,\n currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize\n )\n )\n }\n\n protected processNextChunk(): void {\n const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay } = this\n\n this.processingChunk = true\n this.extractCurrentFileChunk()\n .then((content) => {\n const retryProcessor = new RetryProcessor<any>({\n processor: () => chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content }),\n onSuccess: (chunkResult) => {\n this.processingChunk = false\n if (this.currentChunkNumber < totalChunks) {\n // move to the next chunk also when paused: resume will continue from it\n this.currentChunkNumber += 1\n if (this.running) {\n this.processNextChunk()\n }\n } else {\n this.running = false\n this.completed = true\n // called outside the retried processor: if it throws, the last chunk is not processed again\n this.onComplete?.(chunkResult)\n }\n },\n onFail: (error: Error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n },\n maxTryings,\n retryDelay,\n })\n retryProcessor.start()\n })\n .catch((error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n })\n }\n\n start(startFromChunk: number = 1): void {\n this.running = true\n this.completed = false\n this.currentChunkNumber = startFromChunk\n this.calculateFileSize()\n .then((fileSize) => {\n this.totalFileSize = fileSize\n this.totalChunks = Math.ceil(fileSize / this.chunkSize)\n if (this.totalChunks > 0) {\n this.processNextChunk()\n }\n })\n .catch((error) => {\n this.onFail(error)\n this.running = false\n })\n }\n\n stop(): void {\n this.reset()\n }\n\n pause(): void {\n this.running = false\n }\n\n resume(): void {\n if (this.running || this.completed) return\n this.running = true\n // if a chunk is still being processed, processing continues when it completes\n if (!this.processingChunk && this.totalChunks > 0) {\n this.processNextChunk()\n }\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
+ const toError = (error) => (error instanceof Error ? error : new Error(String(error)));
1
2
  export class RetryProcessor {
2
- constructor({ processor, onSuccess, onFail, maxTryings = 2 }) {
3
+ constructor({ processor, onSuccess, onFail, maxTryings = 2, retryDelay = 0 }) {
3
4
  // State properties
4
5
  this.currentTrying = 0;
5
6
  this.running = false;
@@ -7,6 +8,7 @@ export class RetryProcessor {
7
8
  this.onSuccess = onSuccess;
8
9
  this.onFail = onFail;
9
10
  this.maxTryings = maxTryings;
11
+ this.retryDelay = retryDelay;
10
12
  }
11
13
  reset() {
12
14
  this.currentTrying = 0;
@@ -14,20 +16,36 @@ export class RetryProcessor {
14
16
  }
15
17
  processNext() {
16
18
  this.currentTrying += 1;
17
- this.processor()
18
- .then((res) => {
19
- this.onSuccess(res);
20
- })
21
- .catch((error) => {
22
- // On error, check if we should try again
23
- if (this.running && this.currentTrying < this.maxTryings) {
24
- this.processNext(); // Retry
19
+ // onSuccess runs outside the retried chain: if it throws, the processor must not run again
20
+ this.processor().then((result) => this.handleSuccess(result), (error) => this.handleProcessorError(toError(error)));
21
+ }
22
+ handleSuccess(result) {
23
+ try {
24
+ this.onSuccess(result);
25
+ }
26
+ catch (error) {
27
+ this.onFail(toError(error));
28
+ }
29
+ }
30
+ handleProcessorError(error) {
31
+ if (!this.running || this.currentTrying >= this.maxTryings) {
32
+ // stopped or max attempts reached
33
+ this.onFail(error);
34
+ return;
35
+ }
36
+ const delay = this.retryDelay * Math.pow(2, (this.currentTrying - 1));
37
+ if (delay <= 0) {
38
+ this.processNext();
39
+ return;
40
+ }
41
+ setTimeout(() => {
42
+ if (this.running) {
43
+ this.processNext();
25
44
  }
26
45
  else {
27
- // If we've stopped or exceeded max attempts, call the fail callback
28
46
  this.onFail(error);
29
47
  }
30
- });
48
+ }, delay);
31
49
  }
32
50
  // Public methods
33
51
  start() {
@@ -1 +1 @@
1
- {"version":3,"file":"retryProcessor.js","sourceRoot":"","sources":["../../../src/utils/retryProcessor.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,cAAc;IAUzB,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAuC;QAJjG,mBAAmB;QACX,kBAAa,GAAW,CAAC,CAAA;QACzB,YAAO,GAAY,KAAK,CAAA;QAG9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;IAES,KAAK;QACb,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;IACtB,CAAC;IAES,WAAW;QACnB,IAAI,CAAC,aAAa,IAAI,CAAC,CAAA;QAEvB,IAAI,CAAC,SAAS,EAAE;aACb,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YACZ,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;QACrB,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,yCAAyC;YACzC,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;gBACzD,IAAI,CAAC,WAAW,EAAE,CAAA,CAAC,QAAQ;YAC7B,CAAC;iBAAM,CAAC;gBACN,oEAAoE;gBACpE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACpB,CAAC;QACH,CAAC,CAAC,CAAA;IACN,CAAC;IAED,iBAAiB;IACjB,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;CACF","sourcesContent":["interface RetryProcessorArgs<ProcessorResult> {\n processor: () => Promise<ProcessorResult>\n onSuccess: (result: ProcessorResult) => void\n onFail: (error: Error) => void\n maxTryings?: number\n}\n\nexport class RetryProcessor<ProcessorResult> {\n private readonly processor: () => Promise<ProcessorResult>\n private readonly onSuccess: (result: ProcessorResult) => void\n private readonly onFail: (error: Error) => void\n private readonly maxTryings: number\n\n // State properties\n private currentTrying: number = 0\n private running: boolean = false\n\n constructor({ processor, onSuccess, onFail, maxTryings = 2 }: RetryProcessorArgs<ProcessorResult>) {\n this.processor = processor\n this.onSuccess = onSuccess\n this.onFail = onFail\n this.maxTryings = maxTryings\n }\n\n protected reset() {\n this.currentTrying = 0\n this.running = false\n }\n\n protected processNext(): void {\n this.currentTrying += 1\n\n this.processor()\n .then((res) => {\n this.onSuccess(res)\n })\n .catch((error) => {\n // On error, check if we should try again\n if (this.running && this.currentTrying < this.maxTryings) {\n this.processNext() // Retry\n } else {\n // If we've stopped or exceeded max attempts, call the fail callback\n this.onFail(error)\n }\n })\n }\n\n // Public methods\n start(): void {\n this.running = true\n this.processNext()\n }\n\n stop(): void {\n this.reset()\n }\n}\n"]}
1
+ {"version":3,"file":"retryProcessor.js","sourceRoot":"","sources":["../../../src/utils/retryProcessor.ts"],"names":[],"mappings":"AAYA,MAAM,OAAO,GAAG,CAAC,KAAc,EAAS,EAAE,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAEtG,MAAM,OAAO,cAAc;IAWzB,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,EAAuC;QAJjH,mBAAmB;QACX,kBAAa,GAAW,CAAC,CAAA;QACzB,YAAO,GAAY,KAAK,CAAA;QAG9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;IAES,KAAK;QACb,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;IACtB,CAAC;IAES,WAAW;QACnB,IAAI,CAAC,aAAa,IAAI,CAAC,CAAA;QAEvB,2FAA2F;QAC3F,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACnB,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EACtC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACrD,CAAA;IACH,CAAC;IAEO,aAAa,CAAC,MAAuB;QAC3C,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;IAEO,oBAAoB,CAAC,KAAY;QACvC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3D,kCAAkC;YAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAClB,OAAM;QACR,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,GAAG,SAAA,CAAC,EAAI,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,CAAA,CAAA;QAC7D,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,WAAW,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QACD,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,WAAW,EAAE,CAAA;YACpB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACpB,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAA;IACX,CAAC;IAED,iBAAiB;IACjB,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;CACF","sourcesContent":["interface RetryProcessorArgs<ProcessorResult> {\n processor: () => Promise<ProcessorResult>\n onSuccess: (result: ProcessorResult) => void\n onFail: (error: Error) => void\n maxTryings?: number\n /**\n * Delay (in milliseconds) before the first retry, doubled at every further retry (exponential backoff).\n * Defaults to 0 (immediate retry).\n */\n retryDelay?: number\n}\n\nconst toError = (error: unknown): Error => (error instanceof Error ? error : new Error(String(error)))\n\nexport class RetryProcessor<ProcessorResult> {\n private readonly processor: () => Promise<ProcessorResult>\n private readonly onSuccess: (result: ProcessorResult) => void\n private readonly onFail: (error: Error) => void\n private readonly maxTryings: number\n private readonly retryDelay: number\n\n // State properties\n private currentTrying: number = 0\n private running: boolean = false\n\n constructor({ processor, onSuccess, onFail, maxTryings = 2, retryDelay = 0 }: RetryProcessorArgs<ProcessorResult>) {\n this.processor = processor\n this.onSuccess = onSuccess\n this.onFail = onFail\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n }\n\n protected reset() {\n this.currentTrying = 0\n this.running = false\n }\n\n protected processNext(): void {\n this.currentTrying += 1\n\n // onSuccess runs outside the retried chain: if it throws, the processor must not run again\n this.processor().then(\n (result) => this.handleSuccess(result),\n (error) => this.handleProcessorError(toError(error))\n )\n }\n\n private handleSuccess(result: ProcessorResult): void {\n try {\n this.onSuccess(result)\n } catch (error) {\n this.onFail(toError(error))\n }\n }\n\n private handleProcessorError(error: Error): void {\n if (!this.running || this.currentTrying >= this.maxTryings) {\n // stopped or max attempts reached\n this.onFail(error)\n return\n }\n const delay = this.retryDelay * 2 ** (this.currentTrying - 1)\n if (delay <= 0) {\n this.processNext()\n return\n }\n setTimeout(() => {\n if (this.running) {\n this.processNext()\n } else {\n this.onFail(error)\n }\n }, delay)\n }\n\n // Public methods\n start(): void {\n this.running = true\n this.processNext()\n }\n\n stop(): void {\n this.reset()\n }\n}\n"]}
@@ -12,15 +12,21 @@ export type FileProcessorConstructorArgs = {
12
12
  onComplete?: (result: any) => void;
13
13
  chunkSize?: number;
14
14
  maxTryings?: number;
15
+ /**
16
+ * Delay (in milliseconds) before retrying a failed chunk, doubled at every further retry.
17
+ */
18
+ retryDelay?: number;
15
19
  };
16
20
  export declare class FileProcessor {
17
21
  static readonly defaultChunkSize: number;
18
22
  static readonly defaultMaxTryings = 5;
23
+ static readonly defaultRetryDelay = 1000;
19
24
  private readonly file?;
20
25
  private readonly filePath?;
21
26
  private readonly chunkProcessor;
22
27
  private readonly chunkSize;
23
28
  private readonly maxTryings;
29
+ private readonly retryDelay;
24
30
  private readonly onError?;
25
31
  private readonly onComplete?;
26
32
  protected totalFileSize: number;
@@ -29,7 +35,7 @@ export declare class FileProcessor {
29
35
  protected currentChunkNumber: number;
30
36
  protected processingChunk: boolean;
31
37
  protected completed: boolean;
32
- constructor({ file, filePath, chunkProcessor, onError, onComplete, chunkSize, maxTryings, }: FileProcessorConstructorArgs);
38
+ constructor({ file, filePath, chunkProcessor, onError, onComplete, chunkSize, maxTryings, retryDelay, }: FileProcessorConstructorArgs);
33
39
  protected reset(): void;
34
40
  protected calculateFileSize(): Promise<number>;
35
41
  protected onFail(error: Error): void;
@@ -1,18 +1,9 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  exports.FileProcessor = void 0;
13
4
  const retryProcessor_1 = require("./retryProcessor");
14
5
  class FileProcessor {
15
- constructor({ file, filePath, chunkProcessor, onError, onComplete, chunkSize = FileProcessor.defaultChunkSize, maxTryings = FileProcessor.defaultMaxTryings, }) {
6
+ constructor({ file, filePath, chunkProcessor, onError, onComplete, chunkSize = FileProcessor.defaultChunkSize, maxTryings = FileProcessor.defaultMaxTryings, retryDelay = FileProcessor.defaultRetryDelay, }) {
16
7
  // State properties
17
8
  this.totalFileSize = 0;
18
9
  this.running = false;
@@ -26,6 +17,7 @@ class FileProcessor {
26
17
  this.chunkProcessor = chunkProcessor;
27
18
  this.chunkSize = chunkSize;
28
19
  this.maxTryings = maxTryings;
20
+ this.retryDelay = retryDelay;
29
21
  this.onError = onError;
30
22
  this.onComplete = onComplete;
31
23
  if (!this.file && !this.filePath) {
@@ -58,18 +50,14 @@ class FileProcessor {
58
50
  return Promise.resolve(file.slice((currentChunkNumber - 1) * chunkSize, currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize));
59
51
  }
60
52
  processNextChunk() {
61
- const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings } = this;
53
+ const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay } = this;
62
54
  this.processingChunk = true;
63
55
  this.extractCurrentFileChunk()
64
56
  .then((content) => {
65
57
  const retryProcessor = new retryProcessor_1.RetryProcessor({
66
- processor: () => __awaiter(this, void 0, void 0, function* () {
67
- const chunkResult = yield chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content });
68
- if (this.currentChunkNumber === totalChunks && this.onComplete) {
69
- this.onComplete(chunkResult);
70
- }
71
- }),
72
- onSuccess: () => {
58
+ processor: () => chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content }),
59
+ onSuccess: (chunkResult) => {
60
+ var _a;
73
61
  this.processingChunk = false;
74
62
  if (this.currentChunkNumber < totalChunks) {
75
63
  // move to the next chunk also when paused: resume will continue from it
@@ -81,6 +69,8 @@ class FileProcessor {
81
69
  else {
82
70
  this.running = false;
83
71
  this.completed = true;
72
+ // called outside the retried processor: if it throws, the last chunk is not processed again
73
+ (_a = this.onComplete) === null || _a === void 0 ? void 0 : _a.call(this, chunkResult);
84
74
  }
85
75
  },
86
76
  onFail: (error) => {
@@ -89,6 +79,7 @@ class FileProcessor {
89
79
  this.running = false;
90
80
  },
91
81
  maxTryings,
82
+ retryDelay,
92
83
  });
93
84
  retryProcessor.start();
94
85
  })
@@ -134,4 +125,5 @@ class FileProcessor {
134
125
  exports.FileProcessor = FileProcessor;
135
126
  FileProcessor.defaultChunkSize = 1024 * 1024 * 10; // 10MB
136
127
  FileProcessor.defaultMaxTryings = 5;
128
+ FileProcessor.defaultRetryDelay = 1000; // 1s, then 2s, 4s...
137
129
  //# sourceMappingURL=fileProcessor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fileProcessor.js","sourceRoot":"","sources":["../../src/utils/fileProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { RetryProcessor } from './retryProcessor'\n\ntype ChunkProcessor = (args: {\n chunk: number\n content: Blob | string | Uint8Array\n totalChunks: number\n totalFileSize: number\n}) => Promise<any>\n\nexport type FileProcessorConstructorArgs = {\n file?: File\n filePath?: string\n chunkProcessor: ChunkProcessor\n onError?: (error: Error) => void\n onComplete?: (result: any) => void\n chunkSize?: number\n maxTryings?: number\n}\n\nexport class FileProcessor {\n static readonly defaultChunkSize = 1024 * 1024 * 10 // 10MB\n static readonly defaultMaxTryings = 5\n\n private readonly file?: File\n private readonly filePath?: string\n private readonly chunkProcessor: ChunkProcessor\n private readonly chunkSize: number\n private readonly maxTryings: number\n private readonly onError?: (error: Error) => void\n private readonly onComplete?: (result?: any) => void\n\n // State properties\n protected totalFileSize: number = 0\n protected running: boolean = false\n protected totalChunks: number = 0\n protected currentChunkNumber: number = 0\n // true while a chunk is being processed (resume must not start a second processing chain)\n protected processingChunk: boolean = false\n protected completed: boolean = false\n\n constructor({\n file,\n filePath,\n chunkProcessor,\n onError,\n onComplete,\n chunkSize = FileProcessor.defaultChunkSize,\n maxTryings = FileProcessor.defaultMaxTryings,\n }: FileProcessorConstructorArgs) {\n this.file = file\n this.filePath = filePath\n this.chunkProcessor = chunkProcessor\n this.chunkSize = chunkSize\n this.maxTryings = maxTryings\n this.onError = onError\n this.onComplete = onComplete\n\n if (!this.file && !this.filePath) {\n throw new Error('File or filePath not specified')\n }\n\n this.reset()\n }\n\n protected reset(): void {\n this.running = false\n this.totalChunks = 0\n this.currentChunkNumber = 0\n this.totalFileSize = 0\n this.processingChunk = false\n this.completed = false\n }\n\n protected calculateFileSize(): Promise<number> {\n return Promise.resolve(this.file?.size ?? 0)\n }\n\n protected onFail(error: Error): void {\n this.onError?.(error)\n }\n\n protected extractCurrentFileChunk(): Promise<Blob | string | Uint8Array> {\n const { file, currentChunkNumber, totalChunks, chunkSize } = this\n if (!file) {\n return Promise.reject(new Error('File property not initialized'))\n }\n return Promise.resolve(\n file.slice(\n (currentChunkNumber - 1) * chunkSize,\n currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize\n )\n )\n }\n\n protected processNextChunk(): void {\n const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings } = this\n\n this.processingChunk = true\n this.extractCurrentFileChunk()\n .then((content) => {\n const retryProcessor = new RetryProcessor<void>({\n processor: async () => {\n const chunkResult = await chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content })\n if (this.currentChunkNumber === totalChunks && this.onComplete) {\n this.onComplete(chunkResult)\n }\n },\n onSuccess: () => {\n this.processingChunk = false\n if (this.currentChunkNumber < totalChunks) {\n // move to the next chunk also when paused: resume will continue from it\n this.currentChunkNumber += 1\n if (this.running) {\n this.processNextChunk()\n }\n } else {\n this.running = false\n this.completed = true\n }\n },\n onFail: (error: Error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n },\n maxTryings,\n })\n retryProcessor.start()\n })\n .catch((error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n })\n }\n\n start(startFromChunk: number = 1): void {\n this.running = true\n this.completed = false\n this.currentChunkNumber = startFromChunk\n this.calculateFileSize()\n .then((fileSize) => {\n this.totalFileSize = fileSize\n this.totalChunks = Math.ceil(fileSize / this.chunkSize)\n if (this.totalChunks > 0) {\n this.processNextChunk()\n }\n })\n .catch((error) => {\n this.onFail(error)\n this.running = false\n })\n }\n\n stop(): void {\n this.reset()\n }\n\n pause(): void {\n this.running = false\n }\n\n resume(): void {\n if (this.running || this.completed) return\n this.running = true\n // if a chunk is still being processed, processing continues when it completes\n if (!this.processingChunk && this.totalChunks > 0) {\n this.processNextChunk()\n }\n }\n}\n"]}
1
+ {"version":3,"file":"fileProcessor.js","sourceRoot":"","sources":["../../src/utils/fileProcessor.ts"],"names":[],"mappings":"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","sourcesContent":["import { RetryProcessor } from './retryProcessor'\n\ntype ChunkProcessor = (args: {\n chunk: number\n content: Blob | string | Uint8Array\n totalChunks: number\n totalFileSize: number\n}) => Promise<any>\n\nexport type FileProcessorConstructorArgs = {\n file?: File\n filePath?: string\n chunkProcessor: ChunkProcessor\n onError?: (error: Error) => void\n onComplete?: (result: any) => void\n chunkSize?: number\n maxTryings?: number\n /**\n * Delay (in milliseconds) before retrying a failed chunk, doubled at every further retry.\n */\n retryDelay?: number\n}\n\nexport class FileProcessor {\n static readonly defaultChunkSize = 1024 * 1024 * 10 // 10MB\n static readonly defaultMaxTryings = 5\n static readonly defaultRetryDelay = 1000 // 1s, then 2s, 4s...\n\n private readonly file?: File\n private readonly filePath?: string\n private readonly chunkProcessor: ChunkProcessor\n private readonly chunkSize: number\n private readonly maxTryings: number\n private readonly retryDelay: number\n private readonly onError?: (error: Error) => void\n private readonly onComplete?: (result?: any) => void\n\n // State properties\n protected totalFileSize: number = 0\n protected running: boolean = false\n protected totalChunks: number = 0\n protected currentChunkNumber: number = 0\n // true while a chunk is being processed (resume must not start a second processing chain)\n protected processingChunk: boolean = false\n protected completed: boolean = false\n\n constructor({\n file,\n filePath,\n chunkProcessor,\n onError,\n onComplete,\n chunkSize = FileProcessor.defaultChunkSize,\n maxTryings = FileProcessor.defaultMaxTryings,\n retryDelay = FileProcessor.defaultRetryDelay,\n }: FileProcessorConstructorArgs) {\n this.file = file\n this.filePath = filePath\n this.chunkProcessor = chunkProcessor\n this.chunkSize = chunkSize\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n this.onError = onError\n this.onComplete = onComplete\n\n if (!this.file && !this.filePath) {\n throw new Error('File or filePath not specified')\n }\n\n this.reset()\n }\n\n protected reset(): void {\n this.running = false\n this.totalChunks = 0\n this.currentChunkNumber = 0\n this.totalFileSize = 0\n this.processingChunk = false\n this.completed = false\n }\n\n protected calculateFileSize(): Promise<number> {\n return Promise.resolve(this.file?.size ?? 0)\n }\n\n protected onFail(error: Error): void {\n this.onError?.(error)\n }\n\n protected extractCurrentFileChunk(): Promise<Blob | string | Uint8Array> {\n const { file, currentChunkNumber, totalChunks, chunkSize } = this\n if (!file) {\n return Promise.reject(new Error('File property not initialized'))\n }\n return Promise.resolve(\n file.slice(\n (currentChunkNumber - 1) * chunkSize,\n currentChunkNumber === totalChunks ? undefined : currentChunkNumber * chunkSize\n )\n )\n }\n\n protected processNextChunk(): void {\n const { chunkProcessor, currentChunkNumber, totalFileSize, totalChunks, maxTryings, retryDelay } = this\n\n this.processingChunk = true\n this.extractCurrentFileChunk()\n .then((content) => {\n const retryProcessor = new RetryProcessor<any>({\n processor: () => chunkProcessor({ chunk: currentChunkNumber, totalFileSize, totalChunks, content }),\n onSuccess: (chunkResult) => {\n this.processingChunk = false\n if (this.currentChunkNumber < totalChunks) {\n // move to the next chunk also when paused: resume will continue from it\n this.currentChunkNumber += 1\n if (this.running) {\n this.processNextChunk()\n }\n } else {\n this.running = false\n this.completed = true\n // called outside the retried processor: if it throws, the last chunk is not processed again\n this.onComplete?.(chunkResult)\n }\n },\n onFail: (error: Error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n },\n maxTryings,\n retryDelay,\n })\n retryProcessor.start()\n })\n .catch((error) => {\n this.processingChunk = false\n this.onFail(error)\n this.running = false\n })\n }\n\n start(startFromChunk: number = 1): void {\n this.running = true\n this.completed = false\n this.currentChunkNumber = startFromChunk\n this.calculateFileSize()\n .then((fileSize) => {\n this.totalFileSize = fileSize\n this.totalChunks = Math.ceil(fileSize / this.chunkSize)\n if (this.totalChunks > 0) {\n this.processNextChunk()\n }\n })\n .catch((error) => {\n this.onFail(error)\n this.running = false\n })\n }\n\n stop(): void {\n this.reset()\n }\n\n pause(): void {\n this.running = false\n }\n\n resume(): void {\n if (this.running || this.completed) return\n this.running = true\n // if a chunk is still being processed, processing continues when it completes\n if (!this.processingChunk && this.totalChunks > 0) {\n this.processNextChunk()\n }\n }\n}\n"]}
@@ -3,17 +3,25 @@ interface RetryProcessorArgs<ProcessorResult> {
3
3
  onSuccess: (result: ProcessorResult) => void;
4
4
  onFail: (error: Error) => void;
5
5
  maxTryings?: number;
6
+ /**
7
+ * Delay (in milliseconds) before the first retry, doubled at every further retry (exponential backoff).
8
+ * Defaults to 0 (immediate retry).
9
+ */
10
+ retryDelay?: number;
6
11
  }
7
12
  export declare class RetryProcessor<ProcessorResult> {
8
13
  private readonly processor;
9
14
  private readonly onSuccess;
10
15
  private readonly onFail;
11
16
  private readonly maxTryings;
17
+ private readonly retryDelay;
12
18
  private currentTrying;
13
19
  private running;
14
- constructor({ processor, onSuccess, onFail, maxTryings }: RetryProcessorArgs<ProcessorResult>);
20
+ constructor({ processor, onSuccess, onFail, maxTryings, retryDelay }: RetryProcessorArgs<ProcessorResult>);
15
21
  protected reset(): void;
16
22
  protected processNext(): void;
23
+ private handleSuccess;
24
+ private handleProcessorError;
17
25
  start(): void;
18
26
  stop(): void;
19
27
  }
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RetryProcessor = void 0;
4
+ const toError = (error) => (error instanceof Error ? error : new Error(String(error)));
4
5
  class RetryProcessor {
5
- constructor({ processor, onSuccess, onFail, maxTryings = 2 }) {
6
+ constructor({ processor, onSuccess, onFail, maxTryings = 2, retryDelay = 0 }) {
6
7
  // State properties
7
8
  this.currentTrying = 0;
8
9
  this.running = false;
@@ -10,6 +11,7 @@ class RetryProcessor {
10
11
  this.onSuccess = onSuccess;
11
12
  this.onFail = onFail;
12
13
  this.maxTryings = maxTryings;
14
+ this.retryDelay = retryDelay;
13
15
  }
14
16
  reset() {
15
17
  this.currentTrying = 0;
@@ -17,20 +19,36 @@ class RetryProcessor {
17
19
  }
18
20
  processNext() {
19
21
  this.currentTrying += 1;
20
- this.processor()
21
- .then((res) => {
22
- this.onSuccess(res);
23
- })
24
- .catch((error) => {
25
- // On error, check if we should try again
26
- if (this.running && this.currentTrying < this.maxTryings) {
27
- this.processNext(); // Retry
22
+ // onSuccess runs outside the retried chain: if it throws, the processor must not run again
23
+ this.processor().then((result) => this.handleSuccess(result), (error) => this.handleProcessorError(toError(error)));
24
+ }
25
+ handleSuccess(result) {
26
+ try {
27
+ this.onSuccess(result);
28
+ }
29
+ catch (error) {
30
+ this.onFail(toError(error));
31
+ }
32
+ }
33
+ handleProcessorError(error) {
34
+ if (!this.running || this.currentTrying >= this.maxTryings) {
35
+ // stopped or max attempts reached
36
+ this.onFail(error);
37
+ return;
38
+ }
39
+ const delay = this.retryDelay * Math.pow(2, (this.currentTrying - 1));
40
+ if (delay <= 0) {
41
+ this.processNext();
42
+ return;
43
+ }
44
+ setTimeout(() => {
45
+ if (this.running) {
46
+ this.processNext();
28
47
  }
29
48
  else {
30
- // If we've stopped or exceeded max attempts, call the fail callback
31
49
  this.onFail(error);
32
50
  }
33
- });
51
+ }, delay);
34
52
  }
35
53
  // Public methods
36
54
  start() {
@@ -1 +1 @@
1
- {"version":3,"file":"retryProcessor.js","sourceRoot":"","sources":["../../src/utils/retryProcessor.ts"],"names":[],"mappings":";;;AAOA;IAUE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAuC;QAJjG,mBAAmB;QACX,kBAAa,GAAW,CAAC,CAAA;QACzB,YAAO,GAAY,KAAK,CAAA;QAG9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;IAES,KAAK;QACb,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;IACtB,CAAC;IAES,WAAW;QACnB,IAAI,CAAC,aAAa,IAAI,CAAC,CAAA;QAEvB,IAAI,CAAC,SAAS,EAAE;aACb,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YACZ,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;QACrB,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,yCAAyC;YACzC,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;gBACzD,IAAI,CAAC,WAAW,EAAE,CAAA,CAAC,QAAQ;YAC7B,CAAC;iBAAM,CAAC;gBACN,oEAAoE;gBACpE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACpB,CAAC;QACH,CAAC,CAAC,CAAA;IACN,CAAC;IAED,iBAAiB;IACjB,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;CACF","sourcesContent":["interface RetryProcessorArgs<ProcessorResult> {\n processor: () => Promise<ProcessorResult>\n onSuccess: (result: ProcessorResult) => void\n onFail: (error: Error) => void\n maxTryings?: number\n}\n\nexport class RetryProcessor<ProcessorResult> {\n private readonly processor: () => Promise<ProcessorResult>\n private readonly onSuccess: (result: ProcessorResult) => void\n private readonly onFail: (error: Error) => void\n private readonly maxTryings: number\n\n // State properties\n private currentTrying: number = 0\n private running: boolean = false\n\n constructor({ processor, onSuccess, onFail, maxTryings = 2 }: RetryProcessorArgs<ProcessorResult>) {\n this.processor = processor\n this.onSuccess = onSuccess\n this.onFail = onFail\n this.maxTryings = maxTryings\n }\n\n protected reset() {\n this.currentTrying = 0\n this.running = false\n }\n\n protected processNext(): void {\n this.currentTrying += 1\n\n this.processor()\n .then((res) => {\n this.onSuccess(res)\n })\n .catch((error) => {\n // On error, check if we should try again\n if (this.running && this.currentTrying < this.maxTryings) {\n this.processNext() // Retry\n } else {\n // If we've stopped or exceeded max attempts, call the fail callback\n this.onFail(error)\n }\n })\n }\n\n // Public methods\n start(): void {\n this.running = true\n this.processNext()\n }\n\n stop(): void {\n this.reset()\n }\n}\n"]}
1
+ {"version":3,"file":"retryProcessor.js","sourceRoot":"","sources":["../../src/utils/retryProcessor.ts"],"names":[],"mappings":";;;AAYA,MAAM,OAAO,GAAG,CAAC,KAAc,EAAS,EAAE,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAEtG;IAWE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,EAAuC;QAJjH,mBAAmB;QACX,kBAAa,GAAW,CAAC,CAAA;QACzB,YAAO,GAAY,KAAK,CAAA;QAG9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;IAES,KAAK;QACb,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;IACtB,CAAC;IAES,WAAW;QACnB,IAAI,CAAC,aAAa,IAAI,CAAC,CAAA;QAEvB,2FAA2F;QAC3F,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACnB,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EACtC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CACrD,CAAA;IACH,CAAC;IAEO,aAAa,CAAC,MAAuB;QAC3C,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;IAEO,oBAAoB,CAAC,KAAY;QACvC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3D,kCAAkC;YAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAClB,OAAM;QACR,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,GAAG,SAAA,CAAC,EAAI,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,CAAA,CAAA;QAC7D,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,WAAW,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QACD,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,WAAW,EAAE,CAAA;YACpB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACpB,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAA;IACX,CAAC;IAED,iBAAiB;IACjB,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,WAAW,EAAE,CAAA;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;CACF","sourcesContent":["interface RetryProcessorArgs<ProcessorResult> {\n processor: () => Promise<ProcessorResult>\n onSuccess: (result: ProcessorResult) => void\n onFail: (error: Error) => void\n maxTryings?: number\n /**\n * Delay (in milliseconds) before the first retry, doubled at every further retry (exponential backoff).\n * Defaults to 0 (immediate retry).\n */\n retryDelay?: number\n}\n\nconst toError = (error: unknown): Error => (error instanceof Error ? error : new Error(String(error)))\n\nexport class RetryProcessor<ProcessorResult> {\n private readonly processor: () => Promise<ProcessorResult>\n private readonly onSuccess: (result: ProcessorResult) => void\n private readonly onFail: (error: Error) => void\n private readonly maxTryings: number\n private readonly retryDelay: number\n\n // State properties\n private currentTrying: number = 0\n private running: boolean = false\n\n constructor({ processor, onSuccess, onFail, maxTryings = 2, retryDelay = 0 }: RetryProcessorArgs<ProcessorResult>) {\n this.processor = processor\n this.onSuccess = onSuccess\n this.onFail = onFail\n this.maxTryings = maxTryings\n this.retryDelay = retryDelay\n }\n\n protected reset() {\n this.currentTrying = 0\n this.running = false\n }\n\n protected processNext(): void {\n this.currentTrying += 1\n\n // onSuccess runs outside the retried chain: if it throws, the processor must not run again\n this.processor().then(\n (result) => this.handleSuccess(result),\n (error) => this.handleProcessorError(toError(error))\n )\n }\n\n private handleSuccess(result: ProcessorResult): void {\n try {\n this.onSuccess(result)\n } catch (error) {\n this.onFail(toError(error))\n }\n }\n\n private handleProcessorError(error: Error): void {\n if (!this.running || this.currentTrying >= this.maxTryings) {\n // stopped or max attempts reached\n this.onFail(error)\n return\n }\n const delay = this.retryDelay * 2 ** (this.currentTrying - 1)\n if (delay <= 0) {\n this.processNext()\n return\n }\n setTimeout(() => {\n if (this.running) {\n this.processNext()\n } else {\n this.onFail(error)\n }\n }, delay)\n }\n\n // Public methods\n start(): void {\n this.running = true\n this.processNext()\n }\n\n stop(): void {\n this.reset()\n }\n}\n"]}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "OpenForis",
5
5
  "email": "openforis.arena@gmail.com"
6
6
  },
7
- "version": "2.4.2",
7
+ "version": "2.4.3",
8
8
  "description": "Arena core module",
9
9
  "repository": {
10
10
  "type": "git",