mvc-common-toolkit 1.43.10 → 1.44.0

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 (210) hide show
  1. package/dist/src/constants.d.ts +59 -0
  2. package/dist/src/constants.js +69 -0
  3. package/dist/src/constants.js.map +1 -0
  4. package/dist/src/gateways/alibaba-cloud-gateway.d.ts +30 -0
  5. package/dist/src/gateways/alibaba-cloud-gateway.js +120 -0
  6. package/dist/src/gateways/alibaba-cloud-gateway.js.map +1 -0
  7. package/dist/src/gateways/http-audit-gateway.d.ts +20 -0
  8. package/dist/src/gateways/http-audit-gateway.js +76 -0
  9. package/dist/src/gateways/http-audit-gateway.js.map +1 -0
  10. package/dist/src/gateways/index.d.ts +5 -0
  11. package/dist/src/gateways/index.js +22 -0
  12. package/dist/src/gateways/index.js.map +1 -0
  13. package/dist/src/gateways/internal-auth-gateway.d.ts +8 -0
  14. package/dist/src/gateways/internal-auth-gateway.js +40 -0
  15. package/dist/src/gateways/internal-auth-gateway.js.map +1 -0
  16. package/dist/src/gateways/stdout-audit-gateway.d.ts +7 -0
  17. package/dist/src/gateways/stdout-audit-gateway.js +25 -0
  18. package/dist/src/gateways/stdout-audit-gateway.js.map +1 -0
  19. package/dist/src/gateways/webhook-audit-gateway.d.ts +14 -0
  20. package/dist/src/gateways/webhook-audit-gateway.js +27 -0
  21. package/dist/src/gateways/webhook-audit-gateway.js.map +1 -0
  22. package/dist/src/interfaces.d.ts +218 -0
  23. package/dist/src/interfaces.js +3 -0
  24. package/dist/src/interfaces.js.map +1 -0
  25. package/dist/src/models/audit-log.d.ts +77 -0
  26. package/dist/src/models/audit-log.js +92 -0
  27. package/dist/src/models/audit-log.js.map +1 -0
  28. package/dist/src/models/index.d.ts +1 -0
  29. package/dist/src/models/index.js +18 -0
  30. package/dist/src/models/index.js.map +1 -0
  31. package/dist/src/pkg/array-helper.d.ts +1 -0
  32. package/dist/src/pkg/array-helper.js +12 -0
  33. package/dist/src/pkg/array-helper.js.map +1 -0
  34. package/dist/src/pkg/bcrypt-helper.d.ts +2 -0
  35. package/dist/src/pkg/bcrypt-helper.js +36 -0
  36. package/dist/src/pkg/bcrypt-helper.js.map +1 -0
  37. package/dist/src/pkg/crypto-helper.d.ts +2 -0
  38. package/dist/src/pkg/crypto-helper.js +16 -0
  39. package/dist/src/pkg/crypto-helper.js.map +1 -0
  40. package/dist/src/pkg/encryption-helper.d.ts +18 -0
  41. package/dist/src/pkg/encryption-helper.js +89 -0
  42. package/dist/src/pkg/encryption-helper.js.map +1 -0
  43. package/dist/src/pkg/encryption-helper.spec.d.ts +1 -0
  44. package/dist/src/pkg/encryption-helper.spec.js +238 -0
  45. package/dist/src/pkg/encryption-helper.spec.js.map +1 -0
  46. package/dist/src/pkg/filter-helper.d.ts +2 -0
  47. package/dist/src/pkg/filter-helper.js +102 -0
  48. package/dist/src/pkg/filter-helper.js.map +1 -0
  49. package/dist/src/pkg/filter-helper.spec.d.ts +1 -0
  50. package/dist/src/pkg/filter-helper.spec.js +94 -0
  51. package/dist/src/pkg/filter-helper.spec.js.map +1 -0
  52. package/dist/src/pkg/geoip-helper.d.ts +2 -0
  53. package/dist/src/pkg/geoip-helper.js +32 -0
  54. package/dist/src/pkg/geoip-helper.js.map +1 -0
  55. package/dist/src/pkg/hash-helper.d.ts +1 -0
  56. package/dist/src/pkg/hash-helper.js +37 -0
  57. package/dist/src/pkg/hash-helper.js.map +1 -0
  58. package/dist/src/pkg/http-request-utils.d.ts +4 -0
  59. package/dist/src/pkg/http-request-utils.js +55 -0
  60. package/dist/src/pkg/http-request-utils.js.map +1 -0
  61. package/dist/src/pkg/index.d.ts +19 -0
  62. package/dist/src/pkg/index.js +46 -0
  63. package/dist/src/pkg/index.js.map +1 -0
  64. package/dist/src/pkg/key-helper.d.ts +2 -0
  65. package/dist/src/pkg/key-helper.js +20 -0
  66. package/dist/src/pkg/key-helper.js.map +1 -0
  67. package/dist/src/pkg/logger.d.ts +9 -0
  68. package/dist/src/pkg/logger.js +23 -0
  69. package/dist/src/pkg/logger.js.map +1 -0
  70. package/dist/src/pkg/object-helper.d.ts +2 -0
  71. package/dist/src/pkg/object-helper.js +37 -0
  72. package/dist/src/pkg/object-helper.js.map +1 -0
  73. package/dist/src/pkg/paginated-cache-registry.d.ts +8 -0
  74. package/dist/src/pkg/paginated-cache-registry.js +23 -0
  75. package/dist/src/pkg/paginated-cache-registry.js.map +1 -0
  76. package/dist/src/pkg/query-helper.d.ts +3 -0
  77. package/dist/src/pkg/query-helper.js +60 -0
  78. package/dist/src/pkg/query-helper.js.map +1 -0
  79. package/dist/src/pkg/referral-tree-utils.d.ts +33 -0
  80. package/dist/src/pkg/referral-tree-utils.js +71 -0
  81. package/dist/src/pkg/referral-tree-utils.js.map +1 -0
  82. package/dist/src/pkg/scripts/index.d.ts +1 -0
  83. package/dist/src/pkg/scripts/index.js +28 -0
  84. package/dist/src/pkg/scripts/index.js.map +1 -0
  85. package/dist/src/pkg/scripts/lua.d.ts +10 -0
  86. package/dist/src/pkg/scripts/lua.js +109 -0
  87. package/dist/src/pkg/scripts/lua.js.map +1 -0
  88. package/dist/src/pkg/sort-helper.d.ts +3 -0
  89. package/dist/src/pkg/sort-helper.js +18 -0
  90. package/dist/src/pkg/sort-helper.js.map +1 -0
  91. package/dist/src/pkg/string-utils.d.ts +10 -0
  92. package/dist/src/pkg/string-utils.js +79 -0
  93. package/dist/src/pkg/string-utils.js.map +1 -0
  94. package/dist/src/pkg/task-helper.d.ts +2 -0
  95. package/dist/src/pkg/task-helper.js +30 -0
  96. package/dist/src/pkg/task-helper.js.map +1 -0
  97. package/dist/src/pkg/trading-pair-helper.d.ts +9 -0
  98. package/dist/src/pkg/trading-pair-helper.js +44 -0
  99. package/dist/src/pkg/trading-pair-helper.js.map +1 -0
  100. package/dist/src/pkg/trading-pair-helper.spec.d.ts +1 -0
  101. package/dist/src/pkg/trading-pair-helper.spec.js +132 -0
  102. package/dist/src/pkg/trading-pair-helper.spec.js.map +1 -0
  103. package/dist/src/pkg/workflow/delayed-task-registry.d.ts +10 -0
  104. package/dist/src/pkg/workflow/delayed-task-registry.js +67 -0
  105. package/dist/src/pkg/workflow/delayed-task-registry.js.map +1 -0
  106. package/dist/src/pkg/workflow/delayed-task.d.ts +18 -0
  107. package/dist/src/pkg/workflow/delayed-task.js +95 -0
  108. package/dist/src/pkg/workflow/delayed-task.js.map +1 -0
  109. package/dist/src/pkg/workflow/index.d.ts +5 -0
  110. package/dist/src/pkg/workflow/index.js +22 -0
  111. package/dist/src/pkg/workflow/index.js.map +1 -0
  112. package/dist/src/pkg/workflow/processing-milestone.d.ts +18 -0
  113. package/dist/src/pkg/workflow/processing-milestone.js +39 -0
  114. package/dist/src/pkg/workflow/processing-milestone.js.map +1 -0
  115. package/dist/src/pkg/workflow/retry-task.d.ts +24 -0
  116. package/dist/src/pkg/workflow/retry-task.js +89 -0
  117. package/dist/src/pkg/workflow/retry-task.js.map +1 -0
  118. package/dist/src/pkg/workflow/retry-task.spec.d.ts +1 -0
  119. package/dist/src/pkg/workflow/retry-task.spec.js +145 -0
  120. package/dist/src/pkg/workflow/retry-task.spec.js.map +1 -0
  121. package/dist/src/pkg/workflow/sync-taskqueue.d.ts +32 -0
  122. package/dist/src/pkg/workflow/sync-taskqueue.js +108 -0
  123. package/dist/src/pkg/workflow/sync-taskqueue.js.map +1 -0
  124. package/dist/src/pkg/worksheet.utils.d.ts +27 -0
  125. package/dist/src/pkg/worksheet.utils.js +116 -0
  126. package/dist/src/pkg/worksheet.utils.js.map +1 -0
  127. package/dist/src/services/audit-service.d.ts +7 -0
  128. package/dist/src/services/audit-service.js +32 -0
  129. package/dist/src/services/audit-service.js.map +1 -0
  130. package/dist/src/services/excel.service.d.ts +25 -0
  131. package/dist/src/services/excel.service.js +95 -0
  132. package/dist/src/services/excel.service.js.map +1 -0
  133. package/dist/src/services/http-service.d.ts +7 -0
  134. package/dist/src/services/http-service.js +67 -0
  135. package/dist/src/services/http-service.js.map +1 -0
  136. package/dist/src/services/index.d.ts +8 -0
  137. package/dist/src/services/index.js +25 -0
  138. package/dist/src/services/index.js.map +1 -0
  139. package/dist/src/services/kafka-service.d.ts +15 -0
  140. package/dist/src/services/kafka-service.js +68 -0
  141. package/dist/src/services/kafka-service.js.map +1 -0
  142. package/dist/src/services/mailer-service.d.ts +15 -0
  143. package/dist/src/services/mailer-service.js +44 -0
  144. package/dist/src/services/mailer-service.js.map +1 -0
  145. package/dist/src/services/paginated-cache.d.ts +16 -0
  146. package/dist/src/services/paginated-cache.js +115 -0
  147. package/dist/src/services/paginated-cache.js.map +1 -0
  148. package/dist/src/services/paginated-cache.spec.d.ts +1 -0
  149. package/dist/src/services/paginated-cache.spec.js +284 -0
  150. package/dist/src/services/paginated-cache.spec.js.map +1 -0
  151. package/dist/src/services/redis-service.d.ts +33 -0
  152. package/dist/src/services/redis-service.js +230 -0
  153. package/dist/src/services/redis-service.js.map +1 -0
  154. package/dist/src/services/security-service.d.ts +11 -0
  155. package/dist/src/services/security-service.js +68 -0
  156. package/dist/src/services/security-service.js.map +1 -0
  157. package/dist/tsconfig.tsbuildinfo +1 -1
  158. package/package.json +1 -1
  159. package/src/constants.ts +66 -0
  160. package/src/gateways/alibaba-cloud-gateway.ts +127 -0
  161. package/src/gateways/http-audit-gateway.ts +104 -0
  162. package/src/gateways/index.ts +5 -0
  163. package/src/gateways/internal-auth-gateway.ts +42 -0
  164. package/src/gateways/stdout-audit-gateway.ts +23 -0
  165. package/src/gateways/webhook-audit-gateway.ts +33 -0
  166. package/src/interfaces.ts +304 -0
  167. package/src/models/audit-log.ts +126 -0
  168. package/src/models/index.ts +1 -0
  169. package/src/pkg/array-helper.ts +7 -0
  170. package/src/pkg/bcrypt-helper.ts +9 -0
  171. package/src/pkg/crypto-helper.ts +18 -0
  172. package/src/pkg/encryption-helper.spec.ts +423 -0
  173. package/src/pkg/encryption-helper.ts +155 -0
  174. package/src/pkg/filter-helper.spec.ts +105 -0
  175. package/src/pkg/filter-helper.ts +139 -0
  176. package/src/pkg/geoip-helper.ts +5 -0
  177. package/src/pkg/hash-helper.ts +12 -0
  178. package/src/pkg/http-request-utils.ts +75 -0
  179. package/src/pkg/index.ts +19 -0
  180. package/src/pkg/key-helper.ts +20 -0
  181. package/src/pkg/logger.ts +23 -0
  182. package/src/pkg/object-helper.ts +42 -0
  183. package/src/pkg/paginated-cache-registry.ts +25 -0
  184. package/src/pkg/query-helper.ts +79 -0
  185. package/src/pkg/referral-tree-utils.ts +165 -0
  186. package/src/pkg/scripts/index.ts +1 -0
  187. package/src/pkg/scripts/lua.ts +112 -0
  188. package/src/pkg/sort-helper.ts +19 -0
  189. package/src/pkg/string-utils.ts +104 -0
  190. package/src/pkg/task-helper.ts +25 -0
  191. package/src/pkg/trading-pair-helper.spec.ts +146 -0
  192. package/src/pkg/trading-pair-helper.ts +78 -0
  193. package/src/pkg/workflow/delayed-task-registry.ts +54 -0
  194. package/src/pkg/workflow/delayed-task.ts +106 -0
  195. package/src/pkg/workflow/index.ts +5 -0
  196. package/src/pkg/workflow/processing-milestone.ts +54 -0
  197. package/src/pkg/workflow/retry-task.spec.ts +194 -0
  198. package/src/pkg/workflow/retry-task.ts +119 -0
  199. package/src/pkg/workflow/sync-taskqueue.ts +118 -0
  200. package/src/pkg/worksheet.utils.ts +178 -0
  201. package/src/services/audit-service.ts +22 -0
  202. package/src/services/excel.service.ts +103 -0
  203. package/src/services/http-service.ts +71 -0
  204. package/src/services/index.ts +8 -0
  205. package/src/services/kafka-service.ts +81 -0
  206. package/src/services/mailer-service.ts +43 -0
  207. package/src/services/paginated-cache.spec.ts +519 -0
  208. package/src/services/paginated-cache.ts +122 -0
  209. package/src/services/redis-service.ts +238 -0
  210. package/src/services/security-service.ts +80 -0
@@ -0,0 +1,54 @@
1
+ import { DelayedTaskOps, ITask, TaskRegistry } from "../../interfaces";
2
+ import { DelayedTask } from "./delayed-task";
3
+
4
+ export class DelayedTaskRegistry implements TaskRegistry {
5
+ protected taskRegistry = new Map<string, ITask>();
6
+
7
+ public async register(options: DelayedTaskOps): Promise<ITask> {
8
+ const delayedTask = new DelayedTask(options);
9
+
10
+ this.taskRegistry.set(delayedTask.id, delayedTask);
11
+
12
+ return delayedTask;
13
+ }
14
+
15
+ public async count(): Promise<number> {
16
+ return this.taskRegistry.size;
17
+ }
18
+
19
+ public async getTasks(): Promise<ITask[]> {
20
+ return Object.values(this.taskRegistry);
21
+ }
22
+
23
+ public async getTaskById(id: string): Promise<ITask> {
24
+ return this.taskRegistry.get(id);
25
+ }
26
+
27
+ public async cancelTaskById(id: string): Promise<void> {
28
+ const task = this.taskRegistry.get(id);
29
+ if (!task) {
30
+ throw new Error(`task with id ${id} not exist`);
31
+ }
32
+
33
+ if (!task.isRunning) {
34
+ return;
35
+ }
36
+
37
+ await task.cancel();
38
+
39
+ this.taskRegistry.delete(id);
40
+ }
41
+
42
+ public async startTaskById(id: string): Promise<void> {
43
+ const task = this.taskRegistry.get(id);
44
+ if (!task) {
45
+ throw new Error(`task with id ${id} not exist`);
46
+ }
47
+
48
+ if (task.isRunning) {
49
+ return;
50
+ }
51
+
52
+ return task.start();
53
+ }
54
+ }
@@ -0,0 +1,106 @@
1
+ import { createId } from "@paralleldrive/cuid2";
2
+
3
+ import dayjs from "dayjs";
4
+
5
+ import {
6
+ DelayFn,
7
+ DelayedTaskOps,
8
+ ITask,
9
+ Subscription,
10
+ TaskRescheduleOps,
11
+ } from "../../interfaces";
12
+
13
+ const nativeTimeout: DelayFn = (callback, timeout) => {
14
+ const timeoutId = setTimeout(callback, timeout);
15
+
16
+ return {
17
+ async unsubscribe() {
18
+ clearTimeout(timeoutId);
19
+ },
20
+ };
21
+ };
22
+
23
+ export class DelayedTask implements ITask {
24
+ public id: string;
25
+ public isCron: boolean = false;
26
+ public isRunning: boolean = false;
27
+ public isCancelled: boolean = false;
28
+
29
+ protected _lastRun: Date;
30
+ protected subscription: Subscription;
31
+
32
+ constructor(
33
+ protected options: DelayedTaskOps,
34
+ protected delayFn: DelayFn = nativeTimeout
35
+ ) {
36
+ this.id = createId();
37
+
38
+ if (options?.startOnCreate) {
39
+ this._start();
40
+ }
41
+ }
42
+
43
+ protected _start(): void {
44
+ this.subscription = this.delayFn(() => {
45
+ // run the task
46
+ this.options.callback();
47
+
48
+ // set last run to the moment
49
+ this._lastRun = new Date();
50
+ }, this.options.timeout);
51
+
52
+ this.isRunning = true;
53
+ }
54
+
55
+ public cancel(): Promise<void> {
56
+ this.isCancelled = true;
57
+ this.isRunning = false;
58
+
59
+ return this.subscription.unsubscribe();
60
+ }
61
+
62
+ public async reschedule(options: TaskRescheduleOps): Promise<void> {
63
+ if (!options?.msFromNow && !options?.runTime) {
64
+ throw new Error("must pass either msFromNow or runTime");
65
+ }
66
+
67
+ await this.cancel();
68
+
69
+ if (options.msFromNow && options.runTime) {
70
+ throw new Error("only accept either msFromNow or runTime.");
71
+ }
72
+
73
+ if (options.msFromNow) {
74
+ this.options.timeout = options.msFromNow;
75
+
76
+ return this._start();
77
+ }
78
+
79
+ if (options.runTime) {
80
+ const timeDiff = dayjs(options.runTime).diff(new Date());
81
+ if (timeDiff <= 0) {
82
+ throw new Error("new run time must be after now");
83
+ }
84
+
85
+ this.options.timeout = timeDiff;
86
+
87
+ return this._start();
88
+ }
89
+ }
90
+
91
+ public async start(): Promise<void> {
92
+ this._start();
93
+ }
94
+
95
+ public async lastRun(): Promise<Date> {
96
+ return this._lastRun;
97
+ }
98
+
99
+ public async nextRun(): Promise<Date> {
100
+ if (!this.isRunning) {
101
+ return null;
102
+ }
103
+
104
+ return dayjs(this._lastRun).add(this.options.timeout).toDate();
105
+ }
106
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./delayed-task-registry";
2
+ export * from "./delayed-task";
3
+ export * from "./sync-taskqueue";
4
+ export * from "./retry-task";
5
+ export * from "./processing-milestone";
@@ -0,0 +1,54 @@
1
+ export interface Step {
2
+ name?: string;
3
+ onFailure?: OnFailure;
4
+ }
5
+
6
+ export interface OnFailure {
7
+ reason: string;
8
+ }
9
+
10
+ export class SingleProcessingMilestone {
11
+ protected _steps: Step[];
12
+ protected _isFinished = false;
13
+
14
+ constructor(
15
+ protected pipelineName?: string,
16
+ protected logger?: (msg: string) => void
17
+ ) {}
18
+
19
+ protected throwIfEnded() {
20
+ if (this._isFinished) {
21
+ throw new Error("pipeline has already ended");
22
+ }
23
+ }
24
+
25
+ public get totalSteps(): number {
26
+ this.throwIfEnded();
27
+
28
+ return this._steps.length;
29
+ }
30
+
31
+ public addStep(name?: string, onFailure?: OnFailure): void {
32
+ this.throwIfEnded();
33
+ const stepNo = this.totalSteps + 1;
34
+
35
+ this._steps.push({
36
+ name: name || `step_${stepNo}`,
37
+ onFailure: onFailure || {
38
+ reason: "unknown",
39
+ },
40
+ });
41
+
42
+ if (this.pipelineName && this.logger) {
43
+ this.logger(`step ${stepNo} of pipeline ${this.pipelineName} reached!`);
44
+ }
45
+ }
46
+
47
+ public result(): Step {
48
+ this.throwIfEnded();
49
+
50
+ this._isFinished = true;
51
+
52
+ return this._steps.pop();
53
+ }
54
+ }
@@ -0,0 +1,194 @@
1
+ import chai from "chai";
2
+ import chaiAsPromised from "chai-as-promised";
3
+
4
+ chai.use(chaiAsPromised);
5
+
6
+ const expect = chai.expect;
7
+
8
+ import { RetryTask } from "./retry-task";
9
+
10
+ describe("retry task", () => {
11
+ describe("success cases", () => {
12
+ it("it should resolve correct data if no error occurs", async () => {
13
+ const task = () =>
14
+ new Promise((resolve, reject) => {
15
+ setTimeout(() => resolve("hello world"), 100);
16
+ });
17
+
18
+ const data = "hello world";
19
+
20
+ const retryTask = new RetryTask(task, {
21
+ retryCount: 3,
22
+ taskName: data,
23
+ returnOperationResult: true,
24
+ });
25
+
26
+ const result = await retryTask.run();
27
+
28
+ expect(result.success).to.exist;
29
+ expect(result.success).to.be.true;
30
+ expect(result.data).to.be.eq(data);
31
+ });
32
+
33
+ it("it should resolve operation result with data if no error occurs", async () => {
34
+ const task = () =>
35
+ new Promise((resolve, reject) => {
36
+ setTimeout(() => resolve("hello world"), 100);
37
+ });
38
+
39
+ const data = "hello world";
40
+
41
+ const retryTask = new RetryTask(task, {
42
+ retryCount: 3,
43
+ taskName: data,
44
+ });
45
+
46
+ const result = await retryTask.run();
47
+
48
+ expect(result).to.be.eq(data);
49
+ });
50
+
51
+ it("it should resolve correct data if error occurs within retry count", async () => {
52
+ let retryCount = 0;
53
+ const task = () =>
54
+ new Promise((resolve, reject) => {
55
+ setTimeout(() => {
56
+ retryCount++;
57
+ retryCount === 2 ? resolve("hello world") : reject("not time yet");
58
+ }, 100);
59
+ });
60
+
61
+ const data = "hello world";
62
+
63
+ const retryTask = new RetryTask(task, {
64
+ retryCount: 3,
65
+ taskName: data,
66
+ retryIntervalInMs: 300,
67
+ });
68
+
69
+ const result = await retryTask.run((err) => console.log(err));
70
+
71
+ expect(result).to.be.eq(data);
72
+ });
73
+
74
+ it("should pass if operation result is successful", async () => {
75
+ const data = "hello world";
76
+
77
+ const task = () =>
78
+ new Promise((resolve, reject) => {
79
+ setTimeout(
80
+ () =>
81
+ resolve({
82
+ success: true,
83
+ data,
84
+ }),
85
+ 100
86
+ );
87
+ });
88
+
89
+ const retryTask = new RetryTask(task, {
90
+ retryCount: 3,
91
+ taskName: data,
92
+ });
93
+
94
+ const result = await retryTask.run();
95
+
96
+ expect(result?.data).to.be.eq(data);
97
+ });
98
+ });
99
+
100
+ describe("failure cases", () => {
101
+ it("throws if retry count is exceeded", async () => {
102
+ const task = () =>
103
+ new Promise((resolve, reject) => {
104
+ setTimeout(() => reject(new Error("always fail!")), 100);
105
+ });
106
+
107
+ const data = "hello world";
108
+
109
+ const retryTask = new RetryTask(task, {
110
+ retryCount: 3,
111
+ taskName: data,
112
+ retryIntervalInMs: 300,
113
+ });
114
+
115
+ await expect(retryTask.run()).to.be.rejectedWith(
116
+ "retry task hello world failed after 3 retries"
117
+ );
118
+ });
119
+
120
+ it("returns failed operation result if retry count is exceeded", async () => {
121
+ const task = () =>
122
+ new Promise((resolve, reject) => {
123
+ setTimeout(() => reject(new Error("always fail!")), 100);
124
+ });
125
+
126
+ const data = "hello world";
127
+
128
+ const retryTask = new RetryTask(task, {
129
+ retryCount: 3,
130
+ taskName: data,
131
+ retryIntervalInMs: 300,
132
+ returnOperationResult: true,
133
+ });
134
+
135
+ const failedResult = await retryTask.run();
136
+ expect(failedResult.success).to.exist;
137
+ expect(failedResult.success).to.be.false;
138
+ });
139
+
140
+ it("throws if retry count is exceeded when operation result failed", async () => {
141
+ const task = () =>
142
+ new Promise((resolve, reject) => {
143
+ setTimeout(
144
+ () =>
145
+ resolve({
146
+ success: false,
147
+ message: "failed for some reason",
148
+ }),
149
+ 100
150
+ );
151
+ });
152
+
153
+ const taskName = "hello world";
154
+
155
+ const retryTask = new RetryTask(task, {
156
+ retryCount: 3,
157
+ taskName,
158
+ retryIntervalInMs: 300,
159
+ });
160
+
161
+ await expect(retryTask.run()).to.be.rejectedWith(
162
+ "retry task hello world failed after 3 retries"
163
+ );
164
+ });
165
+
166
+ it("returns failed operation result if retry count is exceeded when operation result failed", async () => {
167
+ const task = () =>
168
+ new Promise((resolve, reject) => {
169
+ setTimeout(
170
+ () =>
171
+ resolve({
172
+ success: false,
173
+ message: "failed for some reason",
174
+ }),
175
+ 100
176
+ );
177
+ });
178
+
179
+ const taskName = "hello world";
180
+
181
+ const retryTask = new RetryTask(task, {
182
+ retryCount: 3,
183
+ taskName,
184
+ retryIntervalInMs: 300,
185
+ returnOperationResult: true,
186
+ });
187
+
188
+ const failedResult = await retryTask.run();
189
+
190
+ expect(failedResult.success).to.exist;
191
+ expect(failedResult.success).to.be.false;
192
+ });
193
+ });
194
+ });
@@ -0,0 +1,119 @@
1
+ import { TaskFn } from "../../interfaces";
2
+
3
+ type TaskErrorHandler = (err: Error) => void | Promise<void>;
4
+ type DoneFn<T = any> = (err: Error, data: T) => void;
5
+
6
+ interface RetryTaskOptions {
7
+ taskName: string;
8
+ retryCount?: number;
9
+ retryIntervalInMs?: number;
10
+ returnOperationResult?: boolean;
11
+ }
12
+
13
+ export class RetryTask {
14
+ constructor(task: TaskFn, protected opts: RetryTaskOptions) {
15
+ this._task = task;
16
+ this._retryCount = opts?.retryCount || 3;
17
+ this._retryIntervalInMs = opts?.retryIntervalInMs || 500;
18
+ this._currentRetryCount = this._retryCount;
19
+ this.taskName = opts?.taskName;
20
+ }
21
+
22
+ protected _task: TaskFn;
23
+ protected _retryCount: number;
24
+ protected _currentRetryCount: number;
25
+ protected _retryIntervalInMs: number;
26
+
27
+ public taskName: string;
28
+
29
+ public get maxRetryCount(): number {
30
+ return this._retryCount;
31
+ }
32
+
33
+ public get retryCount(): number {
34
+ return this._currentRetryCount;
35
+ }
36
+
37
+ public get retryIntervalInMs(): number {
38
+ return this._retryIntervalInMs;
39
+ }
40
+
41
+ public async run<T = any>(
42
+ eachErrorHandler?: TaskErrorHandler,
43
+ allFailedHandler?: TaskErrorHandler
44
+ ): Promise<T> {
45
+ if (this._currentRetryCount === 0) {
46
+ throw new Error("retry task already run");
47
+ }
48
+
49
+ return new Promise(async (resolve, reject) => {
50
+ const doneFn = (err: Error, data: T) => {
51
+ if (err) {
52
+ reject(err);
53
+
54
+ return;
55
+ }
56
+
57
+ resolve(data);
58
+ };
59
+
60
+ this.execTask(doneFn, eachErrorHandler, allFailedHandler);
61
+ });
62
+ }
63
+
64
+ protected async execTask<T = any>(
65
+ done: DoneFn<T>,
66
+ eachErrorHandler: TaskErrorHandler,
67
+ allFailedHandler: TaskErrorHandler
68
+ ): Promise<void> {
69
+ // Max retry reached
70
+ if (this._currentRetryCount === 0) {
71
+ if (this.opts?.returnOperationResult) {
72
+ done(null, {
73
+ success: false,
74
+ message: `retry task ${this.taskName} failed after ${this.maxRetryCount} retries`,
75
+ } as any);
76
+
77
+ return;
78
+ }
79
+
80
+ const error = new Error(
81
+ `retry task ${this.taskName} failed after ${this.maxRetryCount} retries`
82
+ );
83
+
84
+ allFailedHandler?.(error);
85
+ done(error, null);
86
+
87
+ return;
88
+ }
89
+
90
+ try {
91
+ const result = await this._task();
92
+
93
+ // If operation result is returned
94
+ if (result?.success === false) {
95
+ throw new Error(result.message || "execution failed");
96
+ }
97
+
98
+ if (this.opts?.returnOperationResult && !result?.success) {
99
+ done(null, {
100
+ success: true,
101
+ data: result,
102
+ } as any);
103
+
104
+ return;
105
+ }
106
+
107
+ done(null, result);
108
+ } catch (error: any) {
109
+ eachErrorHandler?.(error);
110
+
111
+ setTimeout(
112
+ () => this.execTask(done, eachErrorHandler, allFailedHandler),
113
+ this.retryIntervalInMs
114
+ );
115
+ } finally {
116
+ this._currentRetryCount--;
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,118 @@
1
+ import { EventEmitter } from "stream";
2
+
3
+ import { PinoLogger } from "../logger";
4
+ import { TaskQueue } from "../../interfaces";
5
+
6
+ class Task {
7
+ protected logger = new PinoLogger();
8
+ public isDone = false;
9
+
10
+ constructor(
11
+ public name: string,
12
+ protected handler: any,
13
+ protected doneFn: any
14
+ ) {}
15
+
16
+ public async run() {
17
+ try {
18
+ const data = await this.handler();
19
+
20
+ this.doneFn(null, data);
21
+ } catch (error) {
22
+ this.doneFn(error);
23
+ } finally {
24
+ this.isDone = true;
25
+ }
26
+ }
27
+ }
28
+
29
+ class Queue {
30
+ protected logger = new PinoLogger();
31
+ protected eventEmitter = new EventEmitter();
32
+
33
+ public isRunning: boolean = false;
34
+ public tasks: Task[] = [];
35
+
36
+ protected _interval: any;
37
+
38
+ constructor(protected queueName: string) {}
39
+
40
+ public onExhausted(callback: any) {
41
+ this.eventEmitter.once("exhausted", callback);
42
+ }
43
+
44
+ protected async runNextTask(): Promise<void> {
45
+ try {
46
+ const taskToRun = this.tasks.shift();
47
+ if (!taskToRun) {
48
+ this.isRunning = false;
49
+
50
+ this.eventEmitter.emit("exhausted");
51
+
52
+ return;
53
+ }
54
+
55
+ // Execute the task
56
+ await taskToRun.run();
57
+
58
+ // Run next task
59
+ // Don't want to block the callstack.
60
+ setTimeout(this.runNextTask.bind(this), 0);
61
+ } catch (error: any) {
62
+ this.logger.error(error.message, error.stack);
63
+
64
+ setTimeout(this.runNextTask.bind(this), 0);
65
+ }
66
+ }
67
+
68
+ protected async run(): Promise<void> {
69
+ // The queue is running, don't start new loop
70
+ if (this.isRunning) {
71
+ return;
72
+ }
73
+ this.isRunning = true;
74
+
75
+ // Task queue is exhausted. Start new loop
76
+ await this.runNextTask();
77
+ }
78
+
79
+ public add(task: Task): void {
80
+ this.tasks.push(task);
81
+
82
+ this.run();
83
+ }
84
+ }
85
+
86
+ export class SyncTaskQueue implements TaskQueue {
87
+ protected logger = new PinoLogger();
88
+ protected queueList: Map<string, Queue> = new Map();
89
+
90
+ public push<T>(queueName: string, name: string, handler: any): Promise<T> {
91
+ const queue = this.queueList.get(queueName) || new Queue(queueName);
92
+
93
+ return new Promise((resolve, reject) => {
94
+ const doneFn = (err: Error | null, data: T) => {
95
+ if (err) {
96
+ reject(err);
97
+
98
+ return;
99
+ }
100
+
101
+ resolve(data);
102
+ };
103
+
104
+ const task = new Task(name, handler, doneFn);
105
+
106
+ queue.add(task);
107
+
108
+ if (!this.queueList.has(queueName)) {
109
+ this.queueList.set(queueName, queue);
110
+
111
+ // Once the queue is exhausted, delete the queue to save memory
112
+ queue.onExhausted(() => {
113
+ this.queueList.delete(queueName);
114
+ });
115
+ }
116
+ });
117
+ }
118
+ }