mvc-common-toolkit 1.43.9 → 1.43.11

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 (201) hide show
  1. package/dist/src/constants.d.ts +42 -0
  2. package/dist/src/constants.js +50 -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 +18 -0
  62. package/dist/src/pkg/index.js +45 -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/workflow/delayed-task-registry.d.ts +10 -0
  98. package/dist/src/pkg/workflow/delayed-task-registry.js +67 -0
  99. package/dist/src/pkg/workflow/delayed-task-registry.js.map +1 -0
  100. package/dist/src/pkg/workflow/delayed-task.d.ts +18 -0
  101. package/dist/src/pkg/workflow/delayed-task.js +95 -0
  102. package/dist/src/pkg/workflow/delayed-task.js.map +1 -0
  103. package/dist/src/pkg/workflow/index.d.ts +5 -0
  104. package/dist/src/pkg/workflow/index.js +22 -0
  105. package/dist/src/pkg/workflow/index.js.map +1 -0
  106. package/dist/src/pkg/workflow/processing-milestone.d.ts +18 -0
  107. package/dist/src/pkg/workflow/processing-milestone.js +39 -0
  108. package/dist/src/pkg/workflow/processing-milestone.js.map +1 -0
  109. package/dist/src/pkg/workflow/retry-task.d.ts +24 -0
  110. package/dist/src/pkg/workflow/retry-task.js +89 -0
  111. package/dist/src/pkg/workflow/retry-task.js.map +1 -0
  112. package/dist/src/pkg/workflow/retry-task.spec.d.ts +1 -0
  113. package/dist/src/pkg/workflow/retry-task.spec.js +145 -0
  114. package/dist/src/pkg/workflow/retry-task.spec.js.map +1 -0
  115. package/dist/src/pkg/workflow/sync-taskqueue.d.ts +32 -0
  116. package/dist/src/pkg/workflow/sync-taskqueue.js +108 -0
  117. package/dist/src/pkg/workflow/sync-taskqueue.js.map +1 -0
  118. package/dist/src/pkg/worksheet.utils.d.ts +27 -0
  119. package/dist/src/pkg/worksheet.utils.js +116 -0
  120. package/dist/src/pkg/worksheet.utils.js.map +1 -0
  121. package/dist/src/services/audit-service.d.ts +7 -0
  122. package/dist/src/services/audit-service.js +32 -0
  123. package/dist/src/services/audit-service.js.map +1 -0
  124. package/dist/src/services/excel.service.d.ts +25 -0
  125. package/dist/src/services/excel.service.js +95 -0
  126. package/dist/src/services/excel.service.js.map +1 -0
  127. package/dist/src/services/http-service.d.ts +7 -0
  128. package/dist/src/services/http-service.js +67 -0
  129. package/dist/src/services/http-service.js.map +1 -0
  130. package/dist/src/services/index.d.ts +8 -0
  131. package/dist/src/services/index.js +25 -0
  132. package/dist/src/services/index.js.map +1 -0
  133. package/dist/src/services/kafka-service.d.ts +15 -0
  134. package/dist/src/services/kafka-service.js +68 -0
  135. package/dist/src/services/kafka-service.js.map +1 -0
  136. package/dist/src/services/mailer-service.d.ts +15 -0
  137. package/dist/src/services/mailer-service.js +44 -0
  138. package/dist/src/services/mailer-service.js.map +1 -0
  139. package/dist/src/services/paginated-cache.d.ts +16 -0
  140. package/dist/src/services/paginated-cache.js +115 -0
  141. package/dist/src/services/paginated-cache.js.map +1 -0
  142. package/dist/src/services/paginated-cache.spec.d.ts +1 -0
  143. package/dist/src/services/paginated-cache.spec.js +284 -0
  144. package/dist/src/services/paginated-cache.spec.js.map +1 -0
  145. package/dist/src/services/redis-service.d.ts +33 -0
  146. package/dist/src/services/redis-service.js +230 -0
  147. package/dist/src/services/redis-service.js.map +1 -0
  148. package/dist/src/services/security-service.d.ts +11 -0
  149. package/dist/src/services/security-service.js +68 -0
  150. package/dist/src/services/security-service.js.map +1 -0
  151. package/package.json +1 -1
  152. package/src/constants.ts +47 -0
  153. package/src/gateways/alibaba-cloud-gateway.ts +127 -0
  154. package/src/gateways/http-audit-gateway.ts +104 -0
  155. package/src/gateways/index.ts +5 -0
  156. package/src/gateways/internal-auth-gateway.ts +42 -0
  157. package/src/gateways/stdout-audit-gateway.ts +23 -0
  158. package/src/gateways/webhook-audit-gateway.ts +33 -0
  159. package/src/interfaces.ts +304 -0
  160. package/src/models/audit-log.ts +126 -0
  161. package/src/models/index.ts +1 -0
  162. package/src/pkg/array-helper.ts +7 -0
  163. package/src/pkg/bcrypt-helper.ts +9 -0
  164. package/src/pkg/crypto-helper.ts +18 -0
  165. package/src/pkg/encryption-helper.spec.ts +423 -0
  166. package/src/pkg/encryption-helper.ts +155 -0
  167. package/src/pkg/filter-helper.spec.ts +105 -0
  168. package/src/pkg/filter-helper.ts +139 -0
  169. package/src/pkg/geoip-helper.ts +5 -0
  170. package/src/pkg/hash-helper.ts +12 -0
  171. package/src/pkg/http-request-utils.ts +75 -0
  172. package/src/pkg/index.ts +18 -0
  173. package/src/pkg/key-helper.ts +20 -0
  174. package/src/pkg/logger.ts +23 -0
  175. package/src/pkg/object-helper.ts +42 -0
  176. package/src/pkg/paginated-cache-registry.ts +25 -0
  177. package/src/pkg/query-helper.ts +79 -0
  178. package/src/pkg/referral-tree-utils.ts +165 -0
  179. package/src/pkg/scripts/index.ts +1 -0
  180. package/src/pkg/scripts/lua.ts +112 -0
  181. package/src/pkg/sort-helper.ts +19 -0
  182. package/src/pkg/string-utils.ts +104 -0
  183. package/src/pkg/task-helper.ts +25 -0
  184. package/src/pkg/workflow/delayed-task-registry.ts +54 -0
  185. package/src/pkg/workflow/delayed-task.ts +106 -0
  186. package/src/pkg/workflow/index.ts +5 -0
  187. package/src/pkg/workflow/processing-milestone.ts +54 -0
  188. package/src/pkg/workflow/retry-task.spec.ts +194 -0
  189. package/src/pkg/workflow/retry-task.ts +119 -0
  190. package/src/pkg/workflow/sync-taskqueue.ts +118 -0
  191. package/src/pkg/worksheet.utils.ts +178 -0
  192. package/src/services/audit-service.ts +22 -0
  193. package/src/services/excel.service.ts +103 -0
  194. package/src/services/http-service.ts +71 -0
  195. package/src/services/index.ts +8 -0
  196. package/src/services/kafka-service.ts +81 -0
  197. package/src/services/mailer-service.ts +43 -0
  198. package/src/services/paginated-cache.spec.ts +519 -0
  199. package/src/services/paginated-cache.ts +122 -0
  200. package/src/services/redis-service.ts +238 -0
  201. package/src/services/security-service.ts +80 -0
@@ -0,0 +1,112 @@
1
+ export const IncrByAndSetTTLIfNotExists = `
2
+ if redis.call("EXISTS", KEYS[1]) == 0 then
3
+ redis.call("INCRBY", KEYS[1], ARGV[1])
4
+ redis.call("EXPIRE", KEYS[1], ARGV[2])
5
+ end
6
+
7
+ return redis.call("GET", KEYS[1])
8
+ `;
9
+
10
+ export const IncrByAndEnsureTTLIsSet = `
11
+ local inc = tonumber(ARGV[1])
12
+ local ttl = tonumber(ARGV[2])
13
+
14
+ redis.call("INCRBY", KEYS[1], inc)
15
+
16
+ if redis.call("TTL", KEYS[1]) == -1 then
17
+ redis.call("EXPIRE", KEYS[1], ttl)
18
+ end
19
+
20
+ return tonumber(redis.call("GET", KEYS[1]))
21
+ `;
22
+ export const DecrByAndEnsureTTLIsSet = `
23
+ local inc = tonumber(ARGV[1])
24
+ local ttl = tonumber(ARGV[2])
25
+
26
+ redis.call("DECRBY", KEYS[1], inc)
27
+
28
+ if redis.call("TTL", KEYS[1]) == -1 then
29
+ redis.call("EXPIRE", KEYS[1], ttl)
30
+ end
31
+
32
+ return tonumber(redis.call("GET", KEYS[1]))
33
+ `;
34
+ export const IncrByFloatAndEnsureTTLIsSet = `
35
+ local inc = tonumber(ARGV[1])
36
+ local ttl = tonumber(ARGV[2])
37
+
38
+ redis.call("INCRBYFLOAT", KEYS[1], inc)
39
+
40
+ if redis.call("TTL", KEYS[1]) == -1 then
41
+ redis.call("EXPIRE", KEYS[1], ttl)
42
+ end
43
+
44
+ return tonumber(redis.call("GET", KEYS[1]))
45
+ `;
46
+
47
+ export const IncrByIfExists = `
48
+ if redis.call("EXISTS", KEYS[1]) == 1 then
49
+ redis.call("INCRBY", KEYS[1], ARGV[1])
50
+ end
51
+
52
+ return redis.call("GET", KEYS[1])
53
+ `;
54
+
55
+ export const DecrbyAndSetTTLIfNotExists = `
56
+ if redis.call("EXISTS", KEYS[1]) == 0 then
57
+ redis.call("DECRBY", KEYS[1], ARGV[1])
58
+ redis.call("EXPIRE", KEYS[1], ARGV[2])
59
+ end
60
+
61
+ return redis.call("GET", KEYS[1])
62
+ `;
63
+
64
+ export const DecrbyIfExists = `
65
+ if redis.call("EXISTS", KEYS[1]) == 1 then
66
+ redis.call("DECRBY", KEYS[1], ARGV[1])
67
+ end
68
+
69
+ return redis.call("GET", KEYS[1])
70
+ `;
71
+
72
+ export const IncrByFloatAndSetTTLIfNotExists = `
73
+ if redis.call("EXISTS", KEYS[1]) == 0 then
74
+ redis.call("INCRBYFLOAT", KEYS[1], ARGV[1])
75
+ redis.call("EXPIRE", KEYS[1], ARGV[2])
76
+ end
77
+
78
+ return redis.call("GET", KEYS[1])
79
+ `;
80
+
81
+ export const IncrByFloatIfExists = `
82
+ if redis.call("EXISTS", KEYS[1]) == 1 then
83
+ redis.call("INCRBYFLOAT", KEYS[1], ARGV[1])
84
+ end
85
+
86
+ return redis.call("GET", KEYS[1])
87
+ `;
88
+
89
+ export const RefreshTTLIfBelowThreshold = `
90
+ -- KEYS[1] : key to inspect
91
+ -- ARGV[1] : threshold in seconds (e.g. 10)
92
+ -- ARGV[2] : new TTL in seconds to set (e.g. 60)
93
+
94
+ local ttl = redis.call("TTL", KEYS[1])
95
+
96
+ -- ttl == -2 → key doesn’t exist
97
+ -- ttl == -1 → key has no expiration
98
+ if ttl == -2 then
99
+ return -2 -- key absent
100
+ end
101
+
102
+ local threshold = tonumber(ARGV[1])
103
+ local newTTL = tonumber(ARGV[2])
104
+
105
+ -- If TTL is missing (-1) or below threshold, refresh it
106
+ if ttl == -1 or ttl < threshold then
107
+ redis.call("EXPIRE", KEYS[1], newTTL)
108
+ return newTTL -- return the TTL we just set
109
+ end
110
+
111
+ return ttl -- unchanged TTL
112
+ `;
@@ -0,0 +1,19 @@
1
+ import { ISort, SORT_DIRECTION } from "../interfaces";
2
+
3
+ export const toMongooseSort = (sorts: ISort[]): Record<string, number> => {
4
+ return sorts.reduce((agg, current) => {
5
+ agg[current.columnName] = current.direction === "ASC" ? 1 : -1;
6
+
7
+ return agg;
8
+ }, {});
9
+ };
10
+
11
+ export const toTypeOrmSort = (
12
+ sorts: ISort[]
13
+ ): Record<string, SORT_DIRECTION> => {
14
+ return sorts.reduce((agg, current) => {
15
+ agg[current.columnName] = current.direction;
16
+
17
+ return agg;
18
+ }, {});
19
+ };
@@ -0,0 +1,104 @@
1
+ import { createId } from "@paralleldrive/cuid2";
2
+
3
+ export function generateRandomId(): string {
4
+ return createId();
5
+ }
6
+
7
+ export function generatePassword(length = 16): string {
8
+ const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
9
+ const numbers = "0123456789";
10
+ const symbols = "!@#$%^&*(){}|+-";
11
+ const all = chars + numbers + symbols;
12
+
13
+ let retVal = "";
14
+ retVal += chars.charAt(Math.floor(Math.random() * chars.length));
15
+ retVal += numbers.charAt(Math.floor(Math.random() * numbers.length));
16
+ retVal += symbols.charAt(Math.floor(Math.random() * symbols.length));
17
+
18
+ for (let i = 0; i < length - 3; ++i) {
19
+ retVal += all.charAt(Math.floor(Math.random() * all.length));
20
+ }
21
+ return retVal;
22
+ }
23
+
24
+ function escapeRegexCharacters(str: string): string {
25
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
+ }
27
+
28
+ export function validatePasswordStrengthWithMessage(password: string): string {
29
+ const escapedPassword = escapeRegexCharacters(password);
30
+
31
+ if (escapedPassword.length < 8) {
32
+ return "Password must be at least 8 characters long";
33
+ }
34
+ if (!escapedPassword.match(/[a-z]/)) {
35
+ return "Password must contain at least one lowercase letter";
36
+ }
37
+ if (!escapedPassword.match(/[A-Z]/)) {
38
+ return "Password must contain at least one uppercase letter";
39
+ }
40
+ if (!escapedPassword.match(/[0-9]/)) {
41
+ return "Password must contain at least one number";
42
+ }
43
+ if (!escapedPassword.match(/[^a-zA-Z0-9]/)) {
44
+ return "Password must contain at least one special character";
45
+ }
46
+ return "";
47
+ }
48
+
49
+ const standardlize = (f1: string) =>
50
+ f1.replace(new RegExp("_", "g"), "").toLowerCase();
51
+
52
+ type MaskRule = {
53
+ keys: string[];
54
+ pattern: RegExp;
55
+ replacer: (...groups: string[]) => string;
56
+ };
57
+
58
+ const maskRules: MaskRule[] = [
59
+ {
60
+ keys: [
61
+ "password",
62
+ "Authorization",
63
+ "access_token",
64
+ "refresh_token",
65
+ "signature",
66
+ ],
67
+ pattern: /^(.*)$/,
68
+ replacer: () => "***masked***",
69
+ },
70
+ {
71
+ keys: ["txid", "txnid"],
72
+ pattern: /^(.{10})(.*)(.{36})$/,
73
+ replacer: (_match, prefix, middle, suffix) =>
74
+ `${prefix}${"*".repeat(middle.length)}${suffix}`,
75
+ },
76
+ ];
77
+
78
+ export const maskFn = (
79
+ key: string,
80
+ value: unknown,
81
+ additionalMaskRules: MaskRule[] = []
82
+ ): unknown => {
83
+ // Only process string values
84
+ if (typeof value !== "string") {
85
+ return value;
86
+ }
87
+
88
+ const normalizedKey = standardlize(key);
89
+ // Merge default rules with any additional ones
90
+ const allRules = [...maskRules, ...additionalMaskRules];
91
+
92
+ // Find a rule whose keys match the normalized key
93
+ const rule = allRules.find(({ keys }) =>
94
+ keys.some((k) => standardlize(k) === normalizedKey)
95
+ );
96
+
97
+ // No matching rule? Return the original value
98
+ if (!rule) {
99
+ return value;
100
+ }
101
+
102
+ // Apply pattern and replacer
103
+ return value.replace(rule.pattern, rule.replacer);
104
+ };
@@ -0,0 +1,25 @@
1
+ import { TaskFn } from "../interfaces";
2
+
3
+ export async function runWithTimeout<T = any>(
4
+ handler: TaskFn,
5
+ timeoutInMs: number
6
+ ): Promise<T> {
7
+ return new Promise((resolve, reject) => {
8
+ const timeout = setTimeout(
9
+ () => reject(new Error("task timed out")),
10
+ timeoutInMs
11
+ );
12
+
13
+ handler()
14
+ .then((result: T) => {
15
+ clearTimeout(timeout);
16
+
17
+ resolve(result);
18
+ })
19
+ .catch((error: Error) => {
20
+ clearTimeout(timeout);
21
+
22
+ reject(error);
23
+ });
24
+ });
25
+ }
@@ -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
+ });