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.
- package/dist/src/constants.d.ts +42 -0
- package/dist/src/constants.js +50 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/gateways/alibaba-cloud-gateway.d.ts +30 -0
- package/dist/src/gateways/alibaba-cloud-gateway.js +120 -0
- package/dist/src/gateways/alibaba-cloud-gateway.js.map +1 -0
- package/dist/src/gateways/http-audit-gateway.d.ts +20 -0
- package/dist/src/gateways/http-audit-gateway.js +76 -0
- package/dist/src/gateways/http-audit-gateway.js.map +1 -0
- package/dist/src/gateways/index.d.ts +5 -0
- package/dist/src/gateways/index.js +22 -0
- package/dist/src/gateways/index.js.map +1 -0
- package/dist/src/gateways/internal-auth-gateway.d.ts +8 -0
- package/dist/src/gateways/internal-auth-gateway.js +40 -0
- package/dist/src/gateways/internal-auth-gateway.js.map +1 -0
- package/dist/src/gateways/stdout-audit-gateway.d.ts +7 -0
- package/dist/src/gateways/stdout-audit-gateway.js +25 -0
- package/dist/src/gateways/stdout-audit-gateway.js.map +1 -0
- package/dist/src/gateways/webhook-audit-gateway.d.ts +14 -0
- package/dist/src/gateways/webhook-audit-gateway.js +27 -0
- package/dist/src/gateways/webhook-audit-gateway.js.map +1 -0
- package/dist/src/interfaces.d.ts +218 -0
- package/dist/src/interfaces.js +3 -0
- package/dist/src/interfaces.js.map +1 -0
- package/dist/src/models/audit-log.d.ts +77 -0
- package/dist/src/models/audit-log.js +92 -0
- package/dist/src/models/audit-log.js.map +1 -0
- package/dist/src/models/index.d.ts +1 -0
- package/dist/src/models/index.js +18 -0
- package/dist/src/models/index.js.map +1 -0
- package/dist/src/pkg/array-helper.d.ts +1 -0
- package/dist/src/pkg/array-helper.js +12 -0
- package/dist/src/pkg/array-helper.js.map +1 -0
- package/dist/src/pkg/bcrypt-helper.d.ts +2 -0
- package/dist/src/pkg/bcrypt-helper.js +36 -0
- package/dist/src/pkg/bcrypt-helper.js.map +1 -0
- package/dist/src/pkg/crypto-helper.d.ts +2 -0
- package/dist/src/pkg/crypto-helper.js +16 -0
- package/dist/src/pkg/crypto-helper.js.map +1 -0
- package/dist/src/pkg/encryption-helper.d.ts +18 -0
- package/dist/src/pkg/encryption-helper.js +89 -0
- package/dist/src/pkg/encryption-helper.js.map +1 -0
- package/dist/src/pkg/encryption-helper.spec.d.ts +1 -0
- package/dist/src/pkg/encryption-helper.spec.js +238 -0
- package/dist/src/pkg/encryption-helper.spec.js.map +1 -0
- package/dist/src/pkg/filter-helper.d.ts +2 -0
- package/dist/src/pkg/filter-helper.js +102 -0
- package/dist/src/pkg/filter-helper.js.map +1 -0
- package/dist/src/pkg/filter-helper.spec.d.ts +1 -0
- package/dist/src/pkg/filter-helper.spec.js +94 -0
- package/dist/src/pkg/filter-helper.spec.js.map +1 -0
- package/dist/src/pkg/geoip-helper.d.ts +2 -0
- package/dist/src/pkg/geoip-helper.js +32 -0
- package/dist/src/pkg/geoip-helper.js.map +1 -0
- package/dist/src/pkg/hash-helper.d.ts +1 -0
- package/dist/src/pkg/hash-helper.js +37 -0
- package/dist/src/pkg/hash-helper.js.map +1 -0
- package/dist/src/pkg/http-request-utils.d.ts +4 -0
- package/dist/src/pkg/http-request-utils.js +55 -0
- package/dist/src/pkg/http-request-utils.js.map +1 -0
- package/dist/src/pkg/index.d.ts +18 -0
- package/dist/src/pkg/index.js +45 -0
- package/dist/src/pkg/index.js.map +1 -0
- package/dist/src/pkg/key-helper.d.ts +2 -0
- package/dist/src/pkg/key-helper.js +20 -0
- package/dist/src/pkg/key-helper.js.map +1 -0
- package/dist/src/pkg/logger.d.ts +9 -0
- package/dist/src/pkg/logger.js +23 -0
- package/dist/src/pkg/logger.js.map +1 -0
- package/dist/src/pkg/object-helper.d.ts +2 -0
- package/dist/src/pkg/object-helper.js +37 -0
- package/dist/src/pkg/object-helper.js.map +1 -0
- package/dist/src/pkg/paginated-cache-registry.d.ts +8 -0
- package/dist/src/pkg/paginated-cache-registry.js +23 -0
- package/dist/src/pkg/paginated-cache-registry.js.map +1 -0
- package/dist/src/pkg/query-helper.d.ts +3 -0
- package/dist/src/pkg/query-helper.js +60 -0
- package/dist/src/pkg/query-helper.js.map +1 -0
- package/dist/src/pkg/referral-tree-utils.d.ts +33 -0
- package/dist/src/pkg/referral-tree-utils.js +71 -0
- package/dist/src/pkg/referral-tree-utils.js.map +1 -0
- package/dist/src/pkg/scripts/index.d.ts +1 -0
- package/dist/src/pkg/scripts/index.js +28 -0
- package/dist/src/pkg/scripts/index.js.map +1 -0
- package/dist/src/pkg/scripts/lua.d.ts +10 -0
- package/dist/src/pkg/scripts/lua.js +109 -0
- package/dist/src/pkg/scripts/lua.js.map +1 -0
- package/dist/src/pkg/sort-helper.d.ts +3 -0
- package/dist/src/pkg/sort-helper.js +18 -0
- package/dist/src/pkg/sort-helper.js.map +1 -0
- package/dist/src/pkg/string-utils.d.ts +10 -0
- package/dist/src/pkg/string-utils.js +79 -0
- package/dist/src/pkg/string-utils.js.map +1 -0
- package/dist/src/pkg/task-helper.d.ts +2 -0
- package/dist/src/pkg/task-helper.js +30 -0
- package/dist/src/pkg/task-helper.js.map +1 -0
- package/dist/src/pkg/workflow/delayed-task-registry.d.ts +10 -0
- package/dist/src/pkg/workflow/delayed-task-registry.js +67 -0
- package/dist/src/pkg/workflow/delayed-task-registry.js.map +1 -0
- package/dist/src/pkg/workflow/delayed-task.d.ts +18 -0
- package/dist/src/pkg/workflow/delayed-task.js +95 -0
- package/dist/src/pkg/workflow/delayed-task.js.map +1 -0
- package/dist/src/pkg/workflow/index.d.ts +5 -0
- package/dist/src/pkg/workflow/index.js +22 -0
- package/dist/src/pkg/workflow/index.js.map +1 -0
- package/dist/src/pkg/workflow/processing-milestone.d.ts +18 -0
- package/dist/src/pkg/workflow/processing-milestone.js +39 -0
- package/dist/src/pkg/workflow/processing-milestone.js.map +1 -0
- package/dist/src/pkg/workflow/retry-task.d.ts +24 -0
- package/dist/src/pkg/workflow/retry-task.js +89 -0
- package/dist/src/pkg/workflow/retry-task.js.map +1 -0
- package/dist/src/pkg/workflow/retry-task.spec.d.ts +1 -0
- package/dist/src/pkg/workflow/retry-task.spec.js +145 -0
- package/dist/src/pkg/workflow/retry-task.spec.js.map +1 -0
- package/dist/src/pkg/workflow/sync-taskqueue.d.ts +32 -0
- package/dist/src/pkg/workflow/sync-taskqueue.js +108 -0
- package/dist/src/pkg/workflow/sync-taskqueue.js.map +1 -0
- package/dist/src/pkg/worksheet.utils.d.ts +27 -0
- package/dist/src/pkg/worksheet.utils.js +116 -0
- package/dist/src/pkg/worksheet.utils.js.map +1 -0
- package/dist/src/services/audit-service.d.ts +7 -0
- package/dist/src/services/audit-service.js +32 -0
- package/dist/src/services/audit-service.js.map +1 -0
- package/dist/src/services/excel.service.d.ts +25 -0
- package/dist/src/services/excel.service.js +95 -0
- package/dist/src/services/excel.service.js.map +1 -0
- package/dist/src/services/http-service.d.ts +7 -0
- package/dist/src/services/http-service.js +67 -0
- package/dist/src/services/http-service.js.map +1 -0
- package/dist/src/services/index.d.ts +8 -0
- package/dist/src/services/index.js +25 -0
- package/dist/src/services/index.js.map +1 -0
- package/dist/src/services/kafka-service.d.ts +15 -0
- package/dist/src/services/kafka-service.js +68 -0
- package/dist/src/services/kafka-service.js.map +1 -0
- package/dist/src/services/mailer-service.d.ts +15 -0
- package/dist/src/services/mailer-service.js +44 -0
- package/dist/src/services/mailer-service.js.map +1 -0
- package/dist/src/services/paginated-cache.d.ts +16 -0
- package/dist/src/services/paginated-cache.js +115 -0
- package/dist/src/services/paginated-cache.js.map +1 -0
- package/dist/src/services/paginated-cache.spec.d.ts +1 -0
- package/dist/src/services/paginated-cache.spec.js +284 -0
- package/dist/src/services/paginated-cache.spec.js.map +1 -0
- package/dist/src/services/redis-service.d.ts +33 -0
- package/dist/src/services/redis-service.js +230 -0
- package/dist/src/services/redis-service.js.map +1 -0
- package/dist/src/services/security-service.d.ts +11 -0
- package/dist/src/services/security-service.js +68 -0
- package/dist/src/services/security-service.js.map +1 -0
- package/package.json +1 -1
- package/src/constants.ts +47 -0
- package/src/gateways/alibaba-cloud-gateway.ts +127 -0
- package/src/gateways/http-audit-gateway.ts +104 -0
- package/src/gateways/index.ts +5 -0
- package/src/gateways/internal-auth-gateway.ts +42 -0
- package/src/gateways/stdout-audit-gateway.ts +23 -0
- package/src/gateways/webhook-audit-gateway.ts +33 -0
- package/src/interfaces.ts +304 -0
- package/src/models/audit-log.ts +126 -0
- package/src/models/index.ts +1 -0
- package/src/pkg/array-helper.ts +7 -0
- package/src/pkg/bcrypt-helper.ts +9 -0
- package/src/pkg/crypto-helper.ts +18 -0
- package/src/pkg/encryption-helper.spec.ts +423 -0
- package/src/pkg/encryption-helper.ts +155 -0
- package/src/pkg/filter-helper.spec.ts +105 -0
- package/src/pkg/filter-helper.ts +139 -0
- package/src/pkg/geoip-helper.ts +5 -0
- package/src/pkg/hash-helper.ts +12 -0
- package/src/pkg/http-request-utils.ts +75 -0
- package/src/pkg/index.ts +18 -0
- package/src/pkg/key-helper.ts +20 -0
- package/src/pkg/logger.ts +23 -0
- package/src/pkg/object-helper.ts +42 -0
- package/src/pkg/paginated-cache-registry.ts +25 -0
- package/src/pkg/query-helper.ts +79 -0
- package/src/pkg/referral-tree-utils.ts +165 -0
- package/src/pkg/scripts/index.ts +1 -0
- package/src/pkg/scripts/lua.ts +112 -0
- package/src/pkg/sort-helper.ts +19 -0
- package/src/pkg/string-utils.ts +104 -0
- package/src/pkg/task-helper.ts +25 -0
- package/src/pkg/workflow/delayed-task-registry.ts +54 -0
- package/src/pkg/workflow/delayed-task.ts +106 -0
- package/src/pkg/workflow/index.ts +5 -0
- package/src/pkg/workflow/processing-milestone.ts +54 -0
- package/src/pkg/workflow/retry-task.spec.ts +194 -0
- package/src/pkg/workflow/retry-task.ts +119 -0
- package/src/pkg/workflow/sync-taskqueue.ts +118 -0
- package/src/pkg/worksheet.utils.ts +178 -0
- package/src/services/audit-service.ts +22 -0
- package/src/services/excel.service.ts +103 -0
- package/src/services/http-service.ts +71 -0
- package/src/services/index.ts +8 -0
- package/src/services/kafka-service.ts +81 -0
- package/src/services/mailer-service.ts +43 -0
- package/src/services/paginated-cache.spec.ts +519 -0
- package/src/services/paginated-cache.ts +122 -0
- package/src/services/redis-service.ts +238 -0
- 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,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
|
+
});
|