@similie/hyphen-command-server-types 1.0.0 → 1.0.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.
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +4 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/jwt.d.ts +12 -0
- package/dist/services/jwt.d.ts.map +1 -0
- package/dist/services/jwt.js +36 -0
- package/dist/services/jwt.js.map +1 -0
- package/dist/services/objectq.d.ts +39 -0
- package/dist/services/objectq.d.ts.map +1 -0
- package/dist/services/objectq.js +201 -0
- package/dist/services/objectq.js.map +1 -0
- package/dist/services/secrets.d.ts +14 -0
- package/dist/services/secrets.d.ts.map +1 -0
- package/dist/services/secrets.js +44 -0
- package/dist/services/secrets.js.map +1 -0
- package/dist/services/sql.d.ts +27 -0
- package/dist/services/sql.d.ts.map +1 -0
- package/dist/services/sql.js +86 -0
- package/dist/services/sql.js.map +1 -0
- package/package.json +10 -1
- package/CHANGELOG.md +0 -5
- package/src/index.ts +0 -4
- package/src/models/base-model.ts +0 -13
- package/src/models/index.ts +0 -1
- package/src/modules/index.ts +0 -2
- package/src/modules/loader.ts +0 -67
- package/src/modules/types.ts +0 -40
- package/src/services/index.ts +0 -2
- package/src/services/leader-lock.ts +0 -113
- package/src/services/redis.ts +0 -69
- package/src/tools/index.ts +0 -1
- package/src/tools/utils.ts +0 -349
- package/tsconfig.json +0 -46
package/dist/services/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,OAAO,CAAC;AACtB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC"}
|
package/dist/services/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,OAAO,CAAC;AACtB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import jwt, { type SignOptions } from "jsonwebtoken";
|
|
2
|
+
import { type ExpressResponse } from "@similie/ellipsies";
|
|
3
|
+
export declare const signToken: (payload: object, expiresIn?: SignOptions["expiresIn"]) => string;
|
|
4
|
+
export declare const verifyToken: (token: string) => string | jwt.JwtPayload | null;
|
|
5
|
+
export declare const decodeToken: (token: string) => string | jwt.JwtPayload | null;
|
|
6
|
+
export declare const verifyTokenValidity: (req: {
|
|
7
|
+
headers: {
|
|
8
|
+
authentication?: string;
|
|
9
|
+
};
|
|
10
|
+
user?: any;
|
|
11
|
+
}, res: ExpressResponse) => boolean;
|
|
12
|
+
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/services/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,EAAE,EAAe,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,eAAO,MAAM,SAAS,GACpB,SAAS,MAAM,EACf,YAAW,WAAW,CAAC,WAAW,CAAQ,WAI3C,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,mCAOxC,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,mCAExC,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,KAAK;IACH,OAAO,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,EACD,KAAK,eAAe,YAerB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import jwt, {} from "jsonwebtoken";
|
|
2
|
+
import {} from "@similie/ellipsies";
|
|
3
|
+
export const signToken = (payload, expiresIn = "1h") => {
|
|
4
|
+
const JWT_SECRET = process.env.JWT_CLIENT_SECRET ?? "your_jwt_secret";
|
|
5
|
+
return jwt.sign(payload, JWT_SECRET, { expiresIn });
|
|
6
|
+
};
|
|
7
|
+
export const verifyToken = (token) => {
|
|
8
|
+
try {
|
|
9
|
+
const JWT_SECRET = process.env.JWT_CLIENT_SECRET || "your_jwt_secret";
|
|
10
|
+
return jwt.verify(token, JWT_SECRET);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
export const decodeToken = (token) => {
|
|
17
|
+
return jwt.decode(token);
|
|
18
|
+
};
|
|
19
|
+
export const verifyTokenValidity = (req, res) => {
|
|
20
|
+
const token = req.headers.authentication?.split(" ")[1];
|
|
21
|
+
if (!token)
|
|
22
|
+
return false;
|
|
23
|
+
const decoded = verifyToken(token);
|
|
24
|
+
if (!decoded)
|
|
25
|
+
return false;
|
|
26
|
+
const { exp, user } = decoded;
|
|
27
|
+
if (!exp)
|
|
28
|
+
return false;
|
|
29
|
+
const valid = Date.now() < exp * 1000;
|
|
30
|
+
if (valid) {
|
|
31
|
+
res.locals.user = decoded;
|
|
32
|
+
req.user = decoded && user ? user : undefined;
|
|
33
|
+
}
|
|
34
|
+
return valid;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/services/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,EAAE,EAAiC,MAAM,cAAc,CAAC;AAElE,OAAO,EAAwB,MAAM,oBAAoB,CAAC;AAE1D,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,OAAe,EACf,YAAsC,IAAI,EAC1C,EAAE;IACF,MAAM,UAAU,GAAW,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,iBAAiB,CAAC;IAC9E,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;AACtD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAE,EAAE;IAC3C,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,iBAAiB,CAAC;QACtE,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAE,EAAE;IAC3C,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,GAGC,EACD,GAAoB,EACpB,EAAE;IACF,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,OAAuC,CAAC;IAC9D,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,CAAC;IACtC,IAAI,KAAK,EAAE,CAAC;QACV,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC;QAC1B,GAAG,CAAC,IAAI,GAAG,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAChD,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SimilieQuery — a safe, human-friendly mini rule language for JSON filtering.
|
|
3
|
+
*/
|
|
4
|
+
export declare class SimilieQuery {
|
|
5
|
+
/**
|
|
6
|
+
* Interpolates placeholders like {device.identity} or {config.apn}
|
|
7
|
+
* in a string or object template using deep path lookup.
|
|
8
|
+
*
|
|
9
|
+
* Supports nested objects, arrays, and strings.
|
|
10
|
+
* Escaped braces {{ ... }} are preserved.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const tpl = "Device={device.identity} APN={config.apn}";
|
|
14
|
+
* SimilieQuery.interpolate(tpl, { device: { identity: "A1" }, config: { apn: "timor" } });
|
|
15
|
+
* // → "Device=A1 APN=timor"
|
|
16
|
+
*/
|
|
17
|
+
static interpolate(template: any, data: Record<string, any>): any;
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* @param obj The object to query
|
|
21
|
+
* @param path The path to the property, e.g. "a.b[0].c"
|
|
22
|
+
* @param defaultValue The value to return if the path does not exist
|
|
23
|
+
* @returns The value at the specified path or the default value if not found
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const data = { device: { identity: "abc123", info: [{ name: "sensor1" }] } };
|
|
27
|
+
* console.log(get(data, "device.identity")); // "abc123"
|
|
28
|
+
* console.log(get(data, "device.info[0].name")); // "sensor1"
|
|
29
|
+
* console.log(get(data, "device.info[1].name", "N/A"));// "N/A"
|
|
30
|
+
*/
|
|
31
|
+
static get(obj: any, path: string, defaultValue?: any): any;
|
|
32
|
+
static evaluate(expr: string, data: Record<string, any>): boolean;
|
|
33
|
+
private static tokenize;
|
|
34
|
+
private static parse;
|
|
35
|
+
private static execute;
|
|
36
|
+
private static contains;
|
|
37
|
+
private static resolveValue;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=objectq.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectq.d.ts","sourceRoot":"","sources":["../../src/services/objectq.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,YAAY;IACvB;;;;;;;;;;;OAWG;WACW,WAAW,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IA+BxE;;;;;;;;;;;;OAYG;WACW,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,GAAG,GAAG,GAAG;IAiBlE,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO;IAQjE,OAAO,CAAC,MAAM,CAAC,QAAQ;IAOvB,OAAO,CAAC,MAAM,CAAC,KAAK;IAsDpB,OAAO,CAAC,MAAM,CAAC,OAAO;IAqCtB,OAAO,CAAC,MAAM,CAAC,QAAQ;IAMvB,OAAO,CAAC,MAAM,CAAC,YAAY;CA2B5B"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SimilieQuery — a safe, human-friendly mini rule language for JSON filtering.
|
|
3
|
+
*/
|
|
4
|
+
export class SimilieQuery {
|
|
5
|
+
/**
|
|
6
|
+
* Interpolates placeholders like {device.identity} or {config.apn}
|
|
7
|
+
* in a string or object template using deep path lookup.
|
|
8
|
+
*
|
|
9
|
+
* Supports nested objects, arrays, and strings.
|
|
10
|
+
* Escaped braces {{ ... }} are preserved.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const tpl = "Device={device.identity} APN={config.apn}";
|
|
14
|
+
* SimilieQuery.interpolate(tpl, { device: { identity: "A1" }, config: { apn: "timor" } });
|
|
15
|
+
* // → "Device=A1 APN=timor"
|
|
16
|
+
*/
|
|
17
|
+
static interpolate(template, data) {
|
|
18
|
+
// recursively walk through arrays and objects
|
|
19
|
+
if (Array.isArray(template)) {
|
|
20
|
+
return template.map((v) => this.interpolate(v, data));
|
|
21
|
+
}
|
|
22
|
+
if (template && typeof template === "object") {
|
|
23
|
+
const result = {};
|
|
24
|
+
for (const [k, v] of Object.entries(template)) {
|
|
25
|
+
result[k] = this.interpolate(v, data);
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
// only interpolate strings
|
|
30
|
+
if (typeof template !== "string")
|
|
31
|
+
return template;
|
|
32
|
+
// Replace escaped {{ ... }} → keep braces
|
|
33
|
+
const escapeToken = "__ESCAPED_BRACE__";
|
|
34
|
+
template = template.replace(/\{\{(.*?)\}\}/g, (_, inner) => `{${inner}}${escapeToken}`);
|
|
35
|
+
// Match {path.to.value}
|
|
36
|
+
return template
|
|
37
|
+
.replace(/\{([\w.[\]0-9_-]+)\}/g, (_, path) => {
|
|
38
|
+
const value = this.get(data, path);
|
|
39
|
+
return value !== undefined && value !== null ? String(value) : "";
|
|
40
|
+
})
|
|
41
|
+
.replace(new RegExp(escapeToken, "g"), ""); // clean escape markers
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
* @param obj The object to query
|
|
46
|
+
* @param path The path to the property, e.g. "a.b[0].c"
|
|
47
|
+
* @param defaultValue The value to return if the path does not exist
|
|
48
|
+
* @returns The value at the specified path or the default value if not found
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const data = { device: { identity: "abc123", info: [{ name: "sensor1" }] } };
|
|
52
|
+
* console.log(get(data, "device.identity")); // "abc123"
|
|
53
|
+
* console.log(get(data, "device.info[0].name")); // "sensor1"
|
|
54
|
+
* console.log(get(data, "device.info[1].name", "N/A"));// "N/A"
|
|
55
|
+
*/
|
|
56
|
+
static get(obj, path, defaultValue) {
|
|
57
|
+
if (!obj || typeof path !== "string")
|
|
58
|
+
return defaultValue;
|
|
59
|
+
// Split by dots but support bracket syntax like "a[0].b"
|
|
60
|
+
const keys = path
|
|
61
|
+
.replace(/\[(\w+)\]/g, ".$1") // convert [0] → .0
|
|
62
|
+
.replace(/^\./, "") // strip leading dot
|
|
63
|
+
.split(".");
|
|
64
|
+
let result = obj;
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
if (result != null && Object.prototype.hasOwnProperty.call(result, key)) {
|
|
67
|
+
result = result[key];
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
return defaultValue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
static evaluate(expr, data) {
|
|
76
|
+
if (!expr || !expr.trim())
|
|
77
|
+
return true;
|
|
78
|
+
const tokens = this.tokenize(expr);
|
|
79
|
+
const ast = this.parse(tokens);
|
|
80
|
+
return this.execute(ast, data);
|
|
81
|
+
}
|
|
82
|
+
// ---------------- TOKENIZER ----------------
|
|
83
|
+
static tokenize(input) {
|
|
84
|
+
const re = /\s*(>=|<=|==|!=|>|<|IN|NOT IN|CONTAINS|NOT CONTAINS|\(|\)|AND|OR|[A-Za-z0-9_.]+|"[^"]*"|'[^']*'|\[[^\]]*\]|true|false|null)\s*/gi;
|
|
85
|
+
return input.match(re)?.map((t) => t.trim()) ?? [];
|
|
86
|
+
}
|
|
87
|
+
// ---------------- PARSER ----------------
|
|
88
|
+
static parse(tokens) {
|
|
89
|
+
let i = 0;
|
|
90
|
+
const parseExpression = () => {
|
|
91
|
+
let left = parseTerm();
|
|
92
|
+
while (tokens[i] && /^OR$/i.test(tokens[i] || "")) {
|
|
93
|
+
const op = (tokens[i++] || "").toUpperCase();
|
|
94
|
+
const right = parseTerm();
|
|
95
|
+
left = { op, left, right };
|
|
96
|
+
}
|
|
97
|
+
return left;
|
|
98
|
+
};
|
|
99
|
+
const parseTerm = () => {
|
|
100
|
+
let left = parseFactor();
|
|
101
|
+
while (tokens[i] && /^AND$/i.test(tokens[i] || "")) {
|
|
102
|
+
const op = (tokens[i++] || "").toUpperCase();
|
|
103
|
+
const right = parseFactor();
|
|
104
|
+
left = { op, left, right };
|
|
105
|
+
}
|
|
106
|
+
return left;
|
|
107
|
+
};
|
|
108
|
+
const parseFactor = () => {
|
|
109
|
+
if (tokens[i] === "(") {
|
|
110
|
+
i++;
|
|
111
|
+
const expr = parseExpression();
|
|
112
|
+
i++; // skip ')'
|
|
113
|
+
return expr;
|
|
114
|
+
}
|
|
115
|
+
const left = tokens[i++];
|
|
116
|
+
const opParts = [];
|
|
117
|
+
// support multi-word ops like "NOT IN"
|
|
118
|
+
while (tokens[i] &&
|
|
119
|
+
/^(NOT|IN|CONTAINS)$/i.test(tokens[i] || "") &&
|
|
120
|
+
opParts.join(" ").toUpperCase() !== "NOT CONTAINS") {
|
|
121
|
+
opParts.push((tokens[i++] || "").toUpperCase());
|
|
122
|
+
if (opParts.join(" ") === "NOT CONTAINS")
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
const op = opParts.length ? opParts.join(" ") : tokens[i++];
|
|
126
|
+
const right = tokens[i++];
|
|
127
|
+
return { op: (op || "").toUpperCase(), left, right };
|
|
128
|
+
};
|
|
129
|
+
return parseExpression();
|
|
130
|
+
}
|
|
131
|
+
// ---------------- EXECUTOR ----------------
|
|
132
|
+
static execute(node, data) {
|
|
133
|
+
if (!node)
|
|
134
|
+
return false;
|
|
135
|
+
if (node.op === "AND")
|
|
136
|
+
return this.execute(node.left, data) && this.execute(node.right, data);
|
|
137
|
+
if (node.op === "OR")
|
|
138
|
+
return this.execute(node.left, data) || this.execute(node.right, data);
|
|
139
|
+
const leftVal = this.resolveValue(node.left, data);
|
|
140
|
+
const rightVal = this.resolveValue(node.right, data);
|
|
141
|
+
switch (node.op) {
|
|
142
|
+
case "==":
|
|
143
|
+
return leftVal == rightVal;
|
|
144
|
+
case "!=":
|
|
145
|
+
return leftVal != rightVal;
|
|
146
|
+
case ">":
|
|
147
|
+
return leftVal > rightVal;
|
|
148
|
+
case ">=":
|
|
149
|
+
return leftVal >= rightVal;
|
|
150
|
+
case "<":
|
|
151
|
+
return leftVal < rightVal;
|
|
152
|
+
case "<=":
|
|
153
|
+
return leftVal <= rightVal;
|
|
154
|
+
case "IN":
|
|
155
|
+
return Array.isArray(rightVal) ? rightVal.includes(leftVal) : false;
|
|
156
|
+
case "NOT IN":
|
|
157
|
+
return Array.isArray(rightVal) ? !rightVal.includes(leftVal) : true;
|
|
158
|
+
case "CONTAINS":
|
|
159
|
+
return this.contains(rightVal, leftVal);
|
|
160
|
+
case "NOT CONTAINS":
|
|
161
|
+
return !this.contains(rightVal, leftVal);
|
|
162
|
+
default:
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
static contains(container, value) {
|
|
167
|
+
if (typeof container === "string")
|
|
168
|
+
return container.includes(value);
|
|
169
|
+
if (Array.isArray(container))
|
|
170
|
+
return container.includes(value);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
static resolveValue(token, data) {
|
|
174
|
+
if (token === undefined || token === null)
|
|
175
|
+
return null;
|
|
176
|
+
// Array literal
|
|
177
|
+
if (/^\[.*\]$/.test(token)) {
|
|
178
|
+
try {
|
|
179
|
+
return JSON.parse(token.replace(/'/g, '"'));
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// String literal
|
|
186
|
+
if (/^".*"$|^'.*'$/.test(token))
|
|
187
|
+
return token.slice(1, -1); // remove quotes
|
|
188
|
+
// Boolean
|
|
189
|
+
if (/^(true|false)$/i.test(token))
|
|
190
|
+
return token.toLowerCase() === "true";
|
|
191
|
+
// Null
|
|
192
|
+
if (/^null$/i.test(token))
|
|
193
|
+
return null;
|
|
194
|
+
// Number
|
|
195
|
+
if (!isNaN(Number(token)))
|
|
196
|
+
return Number(token);
|
|
197
|
+
// Otherwise path lookup
|
|
198
|
+
return this.get(data, token);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=objectq.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectq.js","sourceRoot":"","sources":["../../src/services/objectq.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class EnvCrypt {
|
|
2
|
+
private readonly _algorithm;
|
|
3
|
+
private readonly _secretKey;
|
|
4
|
+
private _iv;
|
|
5
|
+
constructor(iv: string);
|
|
6
|
+
get convertIv(): Buffer<ArrayBuffer>;
|
|
7
|
+
private get algorithm();
|
|
8
|
+
private get iv();
|
|
9
|
+
static cipherIv(value?: number): string;
|
|
10
|
+
private get secretKey();
|
|
11
|
+
encrypt(text: any): string;
|
|
12
|
+
decrypt: (hash: string) => string;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/services/secrets.ts"],"names":[],"mappings":"AACA,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,GAAG,CAAS;gBACR,EAAE,EAAE,MAAM;IAMtB,IAAW,SAAS,wBAEnB;IAED,OAAO,KAAK,SAAS,GAEpB;IAED,OAAO,KAAK,EAAE,GAEb;IAED,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAE,MAAW,GAAG,MAAM;IAI3C,OAAO,KAAK,SAAS,GAEpB;IAEM,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM;IAO1B,OAAO,GAAI,MAAM,MAAM,YAiB5B;CACH"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
export class EnvCrypt {
|
|
3
|
+
_algorithm = "aes-256-ctr";
|
|
4
|
+
_secretKey;
|
|
5
|
+
_iv;
|
|
6
|
+
constructor(iv) {
|
|
7
|
+
this._iv = iv;
|
|
8
|
+
this._secretKey =
|
|
9
|
+
process.env.ENV_SECRET_KEY || "6241caa2f4b730f7edcb3e115c0948d5";
|
|
10
|
+
}
|
|
11
|
+
get convertIv() {
|
|
12
|
+
return Buffer.from(this.iv, "hex");
|
|
13
|
+
}
|
|
14
|
+
get algorithm() {
|
|
15
|
+
return this._algorithm;
|
|
16
|
+
}
|
|
17
|
+
get iv() {
|
|
18
|
+
return this._iv;
|
|
19
|
+
}
|
|
20
|
+
static cipherIv(value = 16) {
|
|
21
|
+
return crypto.randomBytes(value).toString("hex");
|
|
22
|
+
}
|
|
23
|
+
get secretKey() {
|
|
24
|
+
return this._secretKey;
|
|
25
|
+
}
|
|
26
|
+
encrypt(text) {
|
|
27
|
+
const iv = this.convertIv;
|
|
28
|
+
const cipher = crypto.createCipheriv(this.algorithm, this.secretKey, iv);
|
|
29
|
+
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
|
|
30
|
+
return encrypted.toString("hex");
|
|
31
|
+
}
|
|
32
|
+
decrypt = (hash) => {
|
|
33
|
+
if (!hash) {
|
|
34
|
+
throw new Error("A String Value is Required");
|
|
35
|
+
}
|
|
36
|
+
const decipher = crypto.createDecipheriv(this.algorithm, this.secretKey, this.convertIv);
|
|
37
|
+
const decrpyted = Buffer.concat([
|
|
38
|
+
decipher.update(Buffer.from(hash, "hex")),
|
|
39
|
+
decipher.final(),
|
|
40
|
+
]);
|
|
41
|
+
return decrpyted.toString();
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/services/secrets.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,MAAM,OAAO,QAAQ;IACF,UAAU,GAAG,aAAa,CAAC;IAC3B,UAAU,CAAS;IAC5B,GAAG,CAAS;IACpB,YAAY,EAAU;QACpB,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,UAAU;YACb,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,kCAAkC,CAAC;IACrE,CAAC;IAED,IAAW,SAAS;QAClB,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,IAAY,SAAS;QACnB,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,IAAY,EAAE;QACZ,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,QAAgB,EAAE;QAChC,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;IAED,IAAY,SAAS;QACnB,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAEM,OAAO,CAAC,IAAS;QACtB,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACzE,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACvE,OAAO,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAEM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE;QAChC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAChD,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CACtC,IAAI,CAAC,SAAS,EACd,IAAI,CAAC,SAAS,EACd,IAAI,CAAC,SAAS,CACf,CAAC;QAEF,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;YAC9B,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACzC,QAAQ,CAAC,KAAK,EAAE;SACjB,CAAC,CAAC;QAEH,OAAO,SAAS,CAAC,QAAQ,EAAE,CAAC;IAC9B,CAAC,CAAC;CACH"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { UUID } from "../models";
|
|
2
|
+
import { Client } from "pg";
|
|
3
|
+
export type ListenerFragments = {
|
|
4
|
+
id: number | UUID;
|
|
5
|
+
station: number;
|
|
6
|
+
date: string;
|
|
7
|
+
table: string;
|
|
8
|
+
schema: string;
|
|
9
|
+
context: string;
|
|
10
|
+
};
|
|
11
|
+
export declare class SQLService {
|
|
12
|
+
private readonly _triggers;
|
|
13
|
+
private readonly _listeners;
|
|
14
|
+
private static instance;
|
|
15
|
+
private readonly _client;
|
|
16
|
+
private listerCallbacks;
|
|
17
|
+
private constructor();
|
|
18
|
+
get client(): Client;
|
|
19
|
+
static get i(): SQLService;
|
|
20
|
+
static setup(triggers?: string[], listeners?: Record<string, string>): SQLService;
|
|
21
|
+
getStartTriggersSQL(): string[];
|
|
22
|
+
private listenToChannel;
|
|
23
|
+
private applyClientListeners;
|
|
24
|
+
seedTriggers: () => Promise<void>;
|
|
25
|
+
addListener: (key: string, cb: (payload: ListenerFragments) => void | Promise<void>) => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../src/services/sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAE5B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,UAAU;IAOnB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAP7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,eAAe,CAEhB;IACP,OAAO;IAYP,IAAW,MAAM,IAAI,MAAM,CAE1B;IAED,WAAkB,CAAC,eAKlB;WAEa,KAAK,CACjB,QAAQ,GAAE,MAAM,EAAO,EACvB,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACrC,UAAU;IAON,mBAAmB,IAAI,MAAM,EAAE;IAItC,OAAO,CAAC,eAAe,CAUrB;IAEF,OAAO,CAAC,oBAAoB,CAiB1B;IAEK,YAAY,QAAa,OAAO,CAAC,IAAI,CAAC,CAc3C;IAEK,WAAW,GAChB,KAAK,MAAM,EACX,IAAI,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KACvD,OAAO,CAAC,IAAI,CAAC,CAOd;CACH"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Client } from "pg";
|
|
2
|
+
export class SQLService {
|
|
3
|
+
_triggers;
|
|
4
|
+
_listeners;
|
|
5
|
+
static instance;
|
|
6
|
+
_client;
|
|
7
|
+
listerCallbacks = {};
|
|
8
|
+
constructor(_triggers = [], _listeners = {}) {
|
|
9
|
+
this._triggers = _triggers;
|
|
10
|
+
this._listeners = _listeners;
|
|
11
|
+
this._client = new Client({
|
|
12
|
+
host: process.env.DB_HOST,
|
|
13
|
+
port: Number(process.env.DB_PORT || 5432),
|
|
14
|
+
user: process.env.DB_USER,
|
|
15
|
+
password: process.env.DB_PASSWORD,
|
|
16
|
+
database: process.env.DB_DATABASE,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
get client() {
|
|
20
|
+
return this._client;
|
|
21
|
+
}
|
|
22
|
+
static get i() {
|
|
23
|
+
if (!this.instance) {
|
|
24
|
+
throw new Error("SQLService not initialized. Call setup() first.");
|
|
25
|
+
}
|
|
26
|
+
return this.instance;
|
|
27
|
+
}
|
|
28
|
+
static setup(triggers = [], listeners = {}) {
|
|
29
|
+
if (!SQLService.instance) {
|
|
30
|
+
SQLService.instance = new SQLService(triggers, listeners);
|
|
31
|
+
}
|
|
32
|
+
return SQLService.instance;
|
|
33
|
+
}
|
|
34
|
+
getStartTriggersSQL() {
|
|
35
|
+
return this._triggers;
|
|
36
|
+
}
|
|
37
|
+
listenToChannel = async (channel) => {
|
|
38
|
+
const rawClient = this._client;
|
|
39
|
+
if (!rawClient) {
|
|
40
|
+
throw new Error("Could not obtain PG raw client. Check TypeORM driver internals.");
|
|
41
|
+
}
|
|
42
|
+
await rawClient.query(`LISTEN ${channel}`);
|
|
43
|
+
console.log(`[PG-LISTENER] Listening for ${channel}`);
|
|
44
|
+
};
|
|
45
|
+
applyClientListeners = () => {
|
|
46
|
+
this.client.on("notification", async (msg) => {
|
|
47
|
+
try {
|
|
48
|
+
// console.log("Notification received:", msg);
|
|
49
|
+
const payload = JSON.parse(msg.payload);
|
|
50
|
+
for (const callback of this.listerCallbacks[payload.context] || []) {
|
|
51
|
+
await callback(payload);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
console.error(`[PG-LISTENER] Failed to process payload:`, err);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
this.client.on("error", (err) => {
|
|
59
|
+
console.error(`[PG-LISTENER] PG error, reconnecting in 5 seconds`, err);
|
|
60
|
+
// setTimeout(() => this.listenToChannel(channel), 5000);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
seedTriggers = async () => {
|
|
64
|
+
const triggers = this.getStartTriggersSQL();
|
|
65
|
+
await this._client.connect();
|
|
66
|
+
for (const triggerSQL of triggers) {
|
|
67
|
+
await this._client.query(triggerSQL);
|
|
68
|
+
}
|
|
69
|
+
for (const key of Object.keys(this._listeners)) {
|
|
70
|
+
if (!this._listeners[key]) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
await this.listenToChannel(this._listeners[key]);
|
|
74
|
+
}
|
|
75
|
+
this.applyClientListeners();
|
|
76
|
+
};
|
|
77
|
+
addListener = async (key, cb) => {
|
|
78
|
+
const channel = this._listeners[key];
|
|
79
|
+
if (!channel) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.listerCallbacks[key] = this.listerCallbacks[key] || [];
|
|
83
|
+
this.listerCallbacks[key].push(cb);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=sql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/services/sql.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@similie/hyphen-command-server-types",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Adds common types for Hyphen Command Center modules.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,14 +20,23 @@
|
|
|
20
20
|
"@similie/model-connect-entities": "^1.1.4",
|
|
21
21
|
"crypto-js": "^4.2.0",
|
|
22
22
|
"ioredis": "^5.8.2",
|
|
23
|
+
"jsonwebtoken": "^9.0.3",
|
|
24
|
+
"pg": "^8.16.3",
|
|
23
25
|
"redis": "^5.10.0",
|
|
24
26
|
"redlock": "5.0.0-beta.2",
|
|
25
27
|
"ts-node-dev": "^2.0.0",
|
|
26
28
|
"uuid": "^13.0.0"
|
|
27
29
|
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
28
35
|
"devDependencies": {
|
|
29
36
|
"@types/crypto-js": "^4.2.2",
|
|
37
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
30
38
|
"@types/node": "^24.7.0",
|
|
39
|
+
"@types/pg": "^8.16.0",
|
|
31
40
|
"standard-version": "^9.5.0",
|
|
32
41
|
"typescript": "^5.9.3"
|
|
33
42
|
}
|
package/CHANGELOG.md
DELETED
package/src/index.ts
DELETED
package/src/models/base-model.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type UUID as _UUID } from "@similie/ellipsies";
|
|
2
|
-
export type UUID = _UUID;
|
|
3
|
-
export interface BaseModel {
|
|
4
|
-
id: number;
|
|
5
|
-
createdAt: Date;
|
|
6
|
-
updatedAt: Date;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface BaseUIDModel {
|
|
10
|
-
uid: UUID; // TODO: needs to be UUID
|
|
11
|
-
createdAt: Date;
|
|
12
|
-
updatedAt: Date;
|
|
13
|
-
}
|
package/src/models/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./base-model";
|
package/src/modules/index.ts
DELETED
package/src/modules/loader.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
// src/modules/loader.ts
|
|
2
|
-
import type { HyphenModule, ModuleContext, LoadedModule } from "./types";
|
|
3
|
-
|
|
4
|
-
export async function loadModulesFromEnv(
|
|
5
|
-
ctx: ModuleContext,
|
|
6
|
-
): Promise<LoadedModule[]> {
|
|
7
|
-
const raw = process.env.HYPHEN_MODULES?.trim();
|
|
8
|
-
if (!raw) return [];
|
|
9
|
-
|
|
10
|
-
const specList = raw
|
|
11
|
-
.split(",")
|
|
12
|
-
.map((s) => s.trim())
|
|
13
|
-
.filter(Boolean);
|
|
14
|
-
|
|
15
|
-
const loaded: LoadedModule[] = [];
|
|
16
|
-
|
|
17
|
-
for (const spec of specList) {
|
|
18
|
-
// spec can be:
|
|
19
|
-
// - "hyphen-rtsp-module"
|
|
20
|
-
// - "@similie/hyphen-rtsp-module"
|
|
21
|
-
// - "file:./dist/my-module.js" (dev)
|
|
22
|
-
try {
|
|
23
|
-
const imported = await import(spec);
|
|
24
|
-
const candidate: HyphenModule =
|
|
25
|
-
imported.default ?? imported.module ?? imported;
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
!candidate ||
|
|
29
|
-
typeof candidate.init !== "function" ||
|
|
30
|
-
!candidate.name
|
|
31
|
-
) {
|
|
32
|
-
ctx.log(`[modules] invalid module export: ${spec}`, { spec });
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
ctx.log(`[modules] init ${candidate.name}`, {
|
|
37
|
-
spec,
|
|
38
|
-
version: candidate.version,
|
|
39
|
-
});
|
|
40
|
-
const res = await candidate.init(ctx);
|
|
41
|
-
loaded.push({ mod: candidate, res });
|
|
42
|
-
ctx.log(`[modules] ready ${candidate.name}`, { spec });
|
|
43
|
-
} catch (e: any) {
|
|
44
|
-
ctx.log(`[modules] failed to load: ${spec}`, {
|
|
45
|
-
error: e?.message ?? String(e),
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return loaded;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function shutdownModules(
|
|
54
|
-
ctx: ModuleContext,
|
|
55
|
-
loaded: LoadedModule[],
|
|
56
|
-
) {
|
|
57
|
-
for (const { mod, res } of loaded) {
|
|
58
|
-
try {
|
|
59
|
-
res && (await res?.shutdown?.());
|
|
60
|
-
ctx.log(`[modules] shutdown ${mod.name}`);
|
|
61
|
-
} catch (e: any) {
|
|
62
|
-
ctx.log(`[modules] shutdown failed ${mod.name}`, {
|
|
63
|
-
error: e?.message ?? String(e),
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
package/src/modules/types.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// src/modules/types.ts
|
|
2
|
-
import type { RedisCache, LeaderElector } from "../services"; // or your Redis wrapper type
|
|
3
|
-
import type { Ellipsies } from "@similie/ellipsies";
|
|
4
|
-
// If you have an Ellipsies instance type, use it. Otherwise keep as `any` and tighten later.
|
|
5
|
-
export type EllipsiesInstance = Ellipsies;
|
|
6
|
-
|
|
7
|
-
export type CommandCenterSystemIdentity = {
|
|
8
|
-
name: string;
|
|
9
|
-
identity: string;
|
|
10
|
-
host: string;
|
|
11
|
-
port: number;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type ModuleContext = {
|
|
15
|
-
ellipsies: EllipsiesInstance;
|
|
16
|
-
redis: typeof RedisCache;
|
|
17
|
-
leader: LeaderElector;
|
|
18
|
-
identity?: CommandCenterSystemIdentity;
|
|
19
|
-
log: (msg: string, extra?: Record<string, any>) => void;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type ModuleInitResult = {
|
|
23
|
-
// Optional cleanup hook called on shutdown
|
|
24
|
-
shutdown?: () => Promise<void> | void;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export interface HyphenModule {
|
|
28
|
-
name: string;
|
|
29
|
-
version?: string;
|
|
30
|
-
|
|
31
|
-
// Called once per process on boot
|
|
32
|
-
init(
|
|
33
|
-
ctx: ModuleContext,
|
|
34
|
-
): Promise<ModuleInitResult | void> | (ModuleInitResult | void);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type LoadedModule = {
|
|
38
|
-
mod: HyphenModule;
|
|
39
|
-
res?: ModuleInitResult | void;
|
|
40
|
-
};
|
package/src/services/index.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "events";
|
|
2
|
-
import { Redis } from "ioredis";
|
|
3
|
-
import Redlock, { Lock } from "redlock";
|
|
4
|
-
import { randomUUID } from "crypto";
|
|
5
|
-
|
|
6
|
-
export type LeaderEvents = {
|
|
7
|
-
elected: () => void;
|
|
8
|
-
revoked: () => void;
|
|
9
|
-
error: (err: Error) => void;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export class LeaderElector extends EventEmitter {
|
|
13
|
-
private static instance: LeaderElector;
|
|
14
|
-
private redlock!: Redlock;
|
|
15
|
-
private redis!: Redis;
|
|
16
|
-
private renewTimer?: NodeJS.Timeout;
|
|
17
|
-
private acquireTimer?: NodeJS.Timeout;
|
|
18
|
-
private lock?: Lock | undefined;
|
|
19
|
-
|
|
20
|
-
private readonly lockKey = "mqtt:leader:lock";
|
|
21
|
-
private readonly ttlMs = 10000; // lock TTL
|
|
22
|
-
private readonly renewEveryMs = 5000; // extend interval (< TTL)
|
|
23
|
-
private readonly retryDelayMs = 1500; // backoff when not leader
|
|
24
|
-
private readonly instanceId = randomUUID(); // for logging/diagnostics
|
|
25
|
-
|
|
26
|
-
private constructor() {
|
|
27
|
-
super();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
static get(): LeaderElector {
|
|
31
|
-
if (!this.instance) this.instance = new LeaderElector();
|
|
32
|
-
return this.instance;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
init(redisUrl: string) {
|
|
36
|
-
this.redis = new Redis(redisUrl);
|
|
37
|
-
this.redlock = new Redlock([this.redis], {
|
|
38
|
-
// Redlock will keep retrying for us when we explicitly request it,
|
|
39
|
-
// but we’ll manage our own retry loop here.
|
|
40
|
-
driftFactor: 0.01,
|
|
41
|
-
retryCount: 0,
|
|
42
|
-
retryDelay: 0,
|
|
43
|
-
});
|
|
44
|
-
this.startAcquireLoop();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Pure check: true only if we *currently* hold a live lock. */
|
|
48
|
-
amLeader(): boolean {
|
|
49
|
-
return !!this.lock;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Stop loops and release if we are leader. */
|
|
53
|
-
async shutdown() {
|
|
54
|
-
clearTimeout(this.acquireTimer as any);
|
|
55
|
-
clearInterval(this.renewTimer as any);
|
|
56
|
-
await this.safeRelease();
|
|
57
|
-
await this.redis?.quit();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// -------- internal --------
|
|
61
|
-
|
|
62
|
-
private startAcquireLoop() {
|
|
63
|
-
const tryAcquire = async () => {
|
|
64
|
-
if (this.lock) return; // already leader
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
// Acquire without retries; we run our own loop.
|
|
68
|
-
const lock = await this.redlock.acquire([this.lockKey], this.ttlMs);
|
|
69
|
-
this.lock = lock;
|
|
70
|
-
this.emit("elected");
|
|
71
|
-
this.startRenewLoop();
|
|
72
|
-
} catch {
|
|
73
|
-
// Not acquired (someone else is leader). Back off a bit (with jitter).
|
|
74
|
-
} finally {
|
|
75
|
-
const jitter = Math.floor(Math.random() * 500);
|
|
76
|
-
this.acquireTimer = setTimeout(tryAcquire, this.retryDelayMs + jitter);
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
tryAcquire().catch((err) => this.emit("error", err as Error));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private startRenewLoop() {
|
|
84
|
-
clearInterval(this.renewTimer as any);
|
|
85
|
-
this.renewTimer = setInterval(async () => {
|
|
86
|
-
if (!this.lock) return;
|
|
87
|
-
try {
|
|
88
|
-
this.lock = await this.lock.extend(this.ttlMs);
|
|
89
|
-
} catch (err) {
|
|
90
|
-
// Couldn’t extend: we lost leadership (expired or Redis issue)
|
|
91
|
-
await this.safeRevoke();
|
|
92
|
-
// acquisition loop is already running, it’ll try to reacquire
|
|
93
|
-
}
|
|
94
|
-
}, this.renewEveryMs);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private async safeRevoke() {
|
|
98
|
-
clearInterval(this.renewTimer as any);
|
|
99
|
-
this.lock = undefined;
|
|
100
|
-
this.emit("revoked");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private async safeRelease() {
|
|
104
|
-
if (!this.lock) return;
|
|
105
|
-
try {
|
|
106
|
-
await this.lock.release();
|
|
107
|
-
} catch {
|
|
108
|
-
// If already expired or released, ignore.
|
|
109
|
-
} finally {
|
|
110
|
-
await this.safeRevoke();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
package/src/services/redis.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// src/services/redis.ts
|
|
2
|
-
import { createClient } from "redis";
|
|
3
|
-
const getRedisConfig = () => {
|
|
4
|
-
return process.env.REDIS_CONFIG_URL || "redis://localhost:6379/1";
|
|
5
|
-
};
|
|
6
|
-
export class RedisCache {
|
|
7
|
-
// Create a Redis client instance.
|
|
8
|
-
private static client = createClient({ url: getRedisConfig() });
|
|
9
|
-
private static DEFAULT_EXPIRATION_SECONDS = 900; // 15 minutes
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Initializes the Redis client. Call this once on server startup.
|
|
13
|
-
*/
|
|
14
|
-
public static async init(): Promise<void> {
|
|
15
|
-
if (this.client.isOpen) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
await this.client.connect();
|
|
20
|
-
console.log("Connected to Redis");
|
|
21
|
-
} catch (err) {
|
|
22
|
-
console.error("Error connecting to Redis:", err);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Caches an object under a specified key.
|
|
28
|
-
* @param key The cache key.
|
|
29
|
-
* @param value The object to cache.
|
|
30
|
-
* @param expirationSeconds Optional expiration time in seconds (defaults to 15 minutes).
|
|
31
|
-
*/
|
|
32
|
-
public static async set(
|
|
33
|
-
key: string,
|
|
34
|
-
value: any,
|
|
35
|
-
expirationSeconds?: number,
|
|
36
|
-
): Promise<void> {
|
|
37
|
-
const exp = expirationSeconds || this.DEFAULT_EXPIRATION_SECONDS;
|
|
38
|
-
const stringValue = JSON.stringify(value);
|
|
39
|
-
await this.client.set(key, stringValue, { EX: exp });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Retrieves a cached object by its key.
|
|
44
|
-
* @param key The cache key.
|
|
45
|
-
* @returns The cached object, or null if not found.
|
|
46
|
-
*/
|
|
47
|
-
public static async get<T>(key: string): Promise<T | null> {
|
|
48
|
-
const data = await this.client.get(key);
|
|
49
|
-
if (data) {
|
|
50
|
-
try {
|
|
51
|
-
return JSON.parse(
|
|
52
|
-
typeof data === "string" ? data : JSON.stringify(data),
|
|
53
|
-
);
|
|
54
|
-
} catch (err) {
|
|
55
|
-
console.error("Error parsing cached data:", err);
|
|
56
|
-
return data as unknown as T | null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Deletes a cache entry.
|
|
64
|
-
* @param key The cache key.
|
|
65
|
-
*/
|
|
66
|
-
public static async del(key: string): Promise<void> {
|
|
67
|
-
await this.client.del(key);
|
|
68
|
-
}
|
|
69
|
-
}
|
package/src/tools/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./utils";
|
package/src/tools/utils.ts
DELETED
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import type { UUID } from "../models";
|
|
2
|
-
import CryptoJS from "crypto-js";
|
|
3
|
-
import { v4 } from "uuid";
|
|
4
|
-
export type MQTTFunctionalResponse = {
|
|
5
|
-
value: any;
|
|
6
|
-
key: string;
|
|
7
|
-
id: string;
|
|
8
|
-
request: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const mqttMessageIdentity = (
|
|
12
|
-
payload: Buffer<ArrayBufferLike>,
|
|
13
|
-
): string => {
|
|
14
|
-
try {
|
|
15
|
-
const value = JSON.parse(payload.toString());
|
|
16
|
-
value._uid = generateUniqueUUID();
|
|
17
|
-
return JSON.stringify(value);
|
|
18
|
-
} catch {
|
|
19
|
-
return payload.toString();
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
24
|
-
|
|
25
|
-
export const wrapModelSet = <T>(
|
|
26
|
-
models: T[],
|
|
27
|
-
wrapOn: keyof T = "uid" as keyof T,
|
|
28
|
-
) => {
|
|
29
|
-
const map = new Map<keyof T, T>();
|
|
30
|
-
models.forEach((m) => map.set(m[wrapOn] as keyof T, m));
|
|
31
|
-
|
|
32
|
-
return (key: keyof T) => {
|
|
33
|
-
if (!map.has(key)) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
return map.get(key);
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const URL_HOST_PATTERN_FOR_INPUT =
|
|
41
|
-
/^(https?:\/\/)?((([a-z0-9]([a-z0-9\-]*[a-z0-9])*)\.)+[a-z]{2,}|(([0-9]{1,3}\.){3}[0-9]{1,3}))(:[0-9]+)?(\/[\-a-z0-9%_.~+]*)*(\?[;&a-z0-9%_.~+=\-]*)?(#[-a-z0-9_]*)?$/;
|
|
42
|
-
export const DOMAIN_PATTERN =
|
|
43
|
-
/^(?:\*\.)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i;
|
|
44
|
-
export function isNumeric(str: string): boolean {
|
|
45
|
-
if (typeof str !== "string") return false; // we only process strings
|
|
46
|
-
const trimmed = str.trim();
|
|
47
|
-
if (trimmed === "") return false;
|
|
48
|
-
return !isNaN(Number(trimmed));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const generateUniqueUUID = () => {
|
|
52
|
-
return v4() as UUID;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export function formatDate(
|
|
56
|
-
date: Date | string,
|
|
57
|
-
locale = "en-AU",
|
|
58
|
-
options: Intl.DateTimeFormatOptions = {
|
|
59
|
-
year: "numeric",
|
|
60
|
-
month: "2-digit",
|
|
61
|
-
day: "2-digit",
|
|
62
|
-
},
|
|
63
|
-
): string {
|
|
64
|
-
if (typeof date === "string") {
|
|
65
|
-
date = new Date(date);
|
|
66
|
-
}
|
|
67
|
-
return date.toLocaleDateString(locale, options);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function formatMoney(value: number, currencySymbol: string = "$") {
|
|
71
|
-
let formattedValue = value.toString();
|
|
72
|
-
if (value.toString().includes(".")) {
|
|
73
|
-
formattedValue = value.toFixed(2);
|
|
74
|
-
} else {
|
|
75
|
-
formattedValue = value.toString() + ".00";
|
|
76
|
-
}
|
|
77
|
-
return `${currencySymbol}${formattedValue}`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const siteMoneyFormatConfig = (
|
|
81
|
-
value: number,
|
|
82
|
-
currencyDivisor: number = 100,
|
|
83
|
-
currencySymbol: string = "$",
|
|
84
|
-
): string => {
|
|
85
|
-
if (typeof value !== "number" && isNaN(value)) {
|
|
86
|
-
return "0.00";
|
|
87
|
-
}
|
|
88
|
-
const divisor = currencyDivisor;
|
|
89
|
-
const formattedValue = (value / divisor).toFixed(2);
|
|
90
|
-
return `${currencySymbol}${formattedValue}`;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
export const errorMessageWithCode = (message: string) => {
|
|
94
|
-
if (!message) {
|
|
95
|
-
return { code: 500, message: message };
|
|
96
|
-
}
|
|
97
|
-
const errorCode = message.split(":");
|
|
98
|
-
if (errorCode.length > 1) {
|
|
99
|
-
const code = errorCode[0] || "500";
|
|
100
|
-
const intValue = parseInt(code);
|
|
101
|
-
return {
|
|
102
|
-
code: Number.isNaN(intValue) ? 500 : intValue,
|
|
103
|
-
message: errorCode[1],
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
return { code: 500, message: message };
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
export const isDomainPattern = (str: string): boolean => {
|
|
110
|
-
if (!str || typeof str !== "string") return false;
|
|
111
|
-
return DOMAIN_PATTERN.test(str);
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
export const extractFormToJson = <T extends Record<string, any> = {}>(
|
|
115
|
-
form: FormData,
|
|
116
|
-
) => {
|
|
117
|
-
const asObject = Object.fromEntries(form.entries());
|
|
118
|
-
const data: any = {};
|
|
119
|
-
for (const key of Object.keys(asObject)) {
|
|
120
|
-
try {
|
|
121
|
-
data[key] = JSON.parse(asObject[key] as string);
|
|
122
|
-
} catch {
|
|
123
|
-
data[key] = asObject[key];
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return data as T;
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
export const delay = (timeout = 1000) =>
|
|
130
|
-
new Promise((resolve) => setTimeout(resolve, timeout));
|
|
131
|
-
|
|
132
|
-
export const generateRandomInt = (min: number, max: number): number => {
|
|
133
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
export const generateUniqueId = (numBytes: number = 16): string => {
|
|
137
|
-
const wordArray = CryptoJS.lib.WordArray.random(numBytes);
|
|
138
|
-
// Convert the WordArray to a hexadecimal string
|
|
139
|
-
return wordArray.toString(CryptoJS.enc.Hex);
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
export const isUUID = (value: string | UUID) => {
|
|
143
|
-
const regex =
|
|
144
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i;
|
|
145
|
-
return regex.test(value);
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
export const validateEmail = (email: string): boolean => {
|
|
149
|
-
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
150
|
-
return emailRegex.test(email);
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
export const generateRandomPassword = (length: number): string => {
|
|
154
|
-
const characters =
|
|
155
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}|;:,.<>?";
|
|
156
|
-
let password = "";
|
|
157
|
-
|
|
158
|
-
for (let i = 0; i < length; i++) {
|
|
159
|
-
const randomIndex = Math.floor(Math.random() * characters.length);
|
|
160
|
-
password += characters[randomIndex];
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return password;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export const fileToUint8Array = (file: File): Promise<Uint8Array> => {
|
|
167
|
-
return new Promise((resolve, reject) => {
|
|
168
|
-
const reader = new FileReader();
|
|
169
|
-
reader.onload = () => {
|
|
170
|
-
const arrayBuffer = reader.result as ArrayBuffer;
|
|
171
|
-
const uint8Array = new Uint8Array(arrayBuffer);
|
|
172
|
-
resolve(uint8Array);
|
|
173
|
-
};
|
|
174
|
-
reader.onerror = () => {
|
|
175
|
-
reject(new Error("Error reading file as ArrayBuffer"));
|
|
176
|
-
};
|
|
177
|
-
reader.readAsArrayBuffer(file);
|
|
178
|
-
});
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
|
182
|
-
return new Promise<ArrayBuffer>((resolve, reject) => {
|
|
183
|
-
const reader = new FileReader();
|
|
184
|
-
|
|
185
|
-
reader.onload = function (event) {
|
|
186
|
-
const arrayBuffer = event.target?.result as ArrayBuffer;
|
|
187
|
-
resolve(arrayBuffer);
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
reader.onerror = function () {
|
|
191
|
-
reject(new Error("Error reading file"));
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
reader.readAsArrayBuffer(file);
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async function sha256Fallback(arrayBuffer: ArrayBuffer) {
|
|
199
|
-
const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer);
|
|
200
|
-
const hash = CryptoJS.SHA256(wordArray);
|
|
201
|
-
return hash.toString(CryptoJS.enc.Hex);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async function sha256(arrayBuffer: ArrayBuffer) {
|
|
205
|
-
if (crypto.subtle) {
|
|
206
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
|
|
207
|
-
return Array.from(new Uint8Array(hashBuffer))
|
|
208
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
209
|
-
.join("");
|
|
210
|
-
} else {
|
|
211
|
-
// Use fallback if crypto.subtle isn't available
|
|
212
|
-
return sha256Fallback(arrayBuffer);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export function sha256String(values: string) {
|
|
217
|
-
const encoder = new TextEncoder();
|
|
218
|
-
const uint8Array = encoder.encode(values);
|
|
219
|
-
const wordArray = CryptoJS.lib.WordArray.create(uint8Array);
|
|
220
|
-
const hash = CryptoJS.SHA256(wordArray);
|
|
221
|
-
return hash.toString(CryptoJS.enc.Hex);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export const ParseSocketMessage = <T>(message: string): T => {
|
|
225
|
-
try {
|
|
226
|
-
return JSON.parse(message) as unknown as T;
|
|
227
|
-
} catch {
|
|
228
|
-
return message as unknown as T;
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
export const stripStateFromObject = (obj: any) => {
|
|
233
|
-
try {
|
|
234
|
-
return JSON.parse(JSON.stringify(obj));
|
|
235
|
-
} catch {
|
|
236
|
-
return obj;
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
export const buildShaValueString = (data: any) => {
|
|
241
|
-
return sha256String(JSON.stringify(data));
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
async function computeHash(arrayBuffer: ArrayBuffer): Promise<string> {
|
|
245
|
-
const hashBuffer = await sha256(arrayBuffer);
|
|
246
|
-
return hashBuffer;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export const hashFileContents = async (file: File): Promise<string> => {
|
|
250
|
-
const arrayBuffer = await readFileAsArrayBuffer(file);
|
|
251
|
-
return computeHash(arrayBuffer);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
export const fileListToFile = async (file: File): Promise<File> => {
|
|
255
|
-
const uint8Array = await fileToUint8Array(file);
|
|
256
|
-
return new File([uint8Array as BlobPart], file.name, {
|
|
257
|
-
type: file.type,
|
|
258
|
-
lastModified: file.lastModified,
|
|
259
|
-
});
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
export const formatMessageServerMessage = (message: string) => {
|
|
263
|
-
const backup = "Failed to send verification email";
|
|
264
|
-
if (!message) {
|
|
265
|
-
return backup;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
const errMessage = JSON.parse(message);
|
|
270
|
-
if (errMessage?.error) {
|
|
271
|
-
return errMessage.error;
|
|
272
|
-
}
|
|
273
|
-
if (errMessage?.message) {
|
|
274
|
-
return errMessage.message;
|
|
275
|
-
}
|
|
276
|
-
throw new Error("Not known json");
|
|
277
|
-
} catch {
|
|
278
|
-
return message;
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
export const isOlderThanInDays = (created_at: Date, days: number): boolean => {
|
|
283
|
-
// Get the current date and time
|
|
284
|
-
const currentDate = new Date();
|
|
285
|
-
|
|
286
|
-
// Calculate the target date by subtracting `days` from the current date
|
|
287
|
-
const targetDate = new Date();
|
|
288
|
-
targetDate.setDate(currentDate.getDate() - days);
|
|
289
|
-
|
|
290
|
-
// Compare created_at with the target date
|
|
291
|
-
return created_at < targetDate;
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
export const removeDoubleSpaces = (text: string) => {
|
|
295
|
-
return text.replace(/\s\s+/g, " ");
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
export const removeContentSpace = (text: string) => {
|
|
299
|
-
return removeDoubleSpaces(text.replace(/[\n\t]/g, " ")); // new lines and tabs
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
export const removeHtmlEntities = (text: string) => {
|
|
303
|
-
return text.replace(/(?:&#x[a-f0-9]{2};|&#[0-9]+;)/g, " ");
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
export function getLast30DaysLabels(locale = "en-US"): string[] {
|
|
307
|
-
const labels: string[] = [];
|
|
308
|
-
const today = new Date();
|
|
309
|
-
|
|
310
|
-
for (let daysAgo = 30; daysAgo >= 0; daysAgo--) {
|
|
311
|
-
const d = new Date(today);
|
|
312
|
-
d.setDate(d.getDate() - daysAgo);
|
|
313
|
-
|
|
314
|
-
// e.g. "Jan"
|
|
315
|
-
const month = d.toLocaleString(locale, { month: "short" });
|
|
316
|
-
// day of month, no leading zero: "1", "31", etc.
|
|
317
|
-
const day = d.getDate();
|
|
318
|
-
|
|
319
|
-
labels.push(`${month}, ${day}`);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return labels;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function humanizeNumber(value: number, precision = 1): string {
|
|
326
|
-
if (value === 0) return "0";
|
|
327
|
-
const suffixes = [
|
|
328
|
-
"",
|
|
329
|
-
" thousand",
|
|
330
|
-
" million",
|
|
331
|
-
" billion",
|
|
332
|
-
" trillion",
|
|
333
|
-
" quadrillion",
|
|
334
|
-
];
|
|
335
|
-
const abs = Math.abs(value);
|
|
336
|
-
const tier = Math.floor(Math.log10(abs) / 3);
|
|
337
|
-
|
|
338
|
-
// if in the ones (no suffix)
|
|
339
|
-
if (tier === 0) {
|
|
340
|
-
return value.toString();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const scale = 10 ** (tier * 3);
|
|
344
|
-
const scaled = value / scale;
|
|
345
|
-
// format to `precision` decimals, then drop any trailing “.0”
|
|
346
|
-
const formatted = scaled.toFixed(precision).replace(/\.0+$/, "");
|
|
347
|
-
|
|
348
|
-
return `${formatted}${suffixes[tier]}`;
|
|
349
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
// File Layout
|
|
5
|
-
"rootDir": "./src",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"moduleResolution": "Node",
|
|
8
|
-
|
|
9
|
-
// Environment Settings
|
|
10
|
-
// See also https://aka.ms/tsconfig/module
|
|
11
|
-
"module": "ESNext",
|
|
12
|
-
"target": "ES2022",
|
|
13
|
-
"types": [],
|
|
14
|
-
// For nodejs:
|
|
15
|
-
// "lib": ["esnext"],
|
|
16
|
-
// "types": ["node"],
|
|
17
|
-
// and npm install -D @types/node
|
|
18
|
-
|
|
19
|
-
// Other Outputs
|
|
20
|
-
"sourceMap": true,
|
|
21
|
-
"declaration": true,
|
|
22
|
-
"declarationMap": true,
|
|
23
|
-
|
|
24
|
-
// Stricter Typechecking Options
|
|
25
|
-
"noUncheckedIndexedAccess": true,
|
|
26
|
-
"exactOptionalPropertyTypes": true,
|
|
27
|
-
"esModuleInterop": true,
|
|
28
|
-
// Style Options
|
|
29
|
-
// "noImplicitReturns": true,
|
|
30
|
-
// "noImplicitOverride": true,
|
|
31
|
-
// "noUnusedLocals": true,
|
|
32
|
-
// "noUnusedParameters": true,
|
|
33
|
-
// "noFallthroughCasesInSwitch": true,
|
|
34
|
-
// "noPropertyAccessFromIndexSignature": true,
|
|
35
|
-
|
|
36
|
-
// Recommended Options
|
|
37
|
-
"strict": true,
|
|
38
|
-
"jsx": "react-jsx",
|
|
39
|
-
"verbatimModuleSyntax": true,
|
|
40
|
-
"isolatedModules": true,
|
|
41
|
-
"noUncheckedSideEffectImports": true,
|
|
42
|
-
"moduleDetection": "force",
|
|
43
|
-
"skipLibCheck": true
|
|
44
|
-
},
|
|
45
|
-
"include": ["src"]
|
|
46
|
-
}
|