@canmingir/link-express 1.6.11 → 1.7.1
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/.eslintrc.ts +29 -0
- package/bin/event-listener.ts +10 -0
- package/index.ts +2 -0
- package/package.json +66 -12
- package/{prepare.js → prepare.ts} +1 -1
- package/src/{authorization.js → authorization.ts} +31 -9
- package/src/config.ts +112 -0
- package/src/dynamodb.ts +24 -0
- package/src/{error.js → error.ts} +30 -11
- package/src/event/client/adapters/KafkaAdapter.ts +140 -0
- package/src/event/client/adapters/SocketAdapter.ts +59 -0
- package/src/event/client/adapters/TxEventQAdapter.ts +232 -0
- package/src/event/client/eventManager.ts +244 -0
- package/src/event/client/index.ts +50 -0
- package/src/event/client/metrics.ts +171 -0
- package/src/event/client/types/types.ts +43 -0
- package/src/event/index.ts +3 -0
- package/src/event/server/server.ts +55 -0
- package/src/event/src/Event.ts +201 -0
- package/src/express.ts +66 -0
- package/src/lib/{settings.js → settings.ts} +16 -4
- package/src/lib/test.ts +68 -0
- package/src/{logger.js → logger.ts} +11 -9
- package/src/metrics/{dbMetrics.js → dbMetrics.ts} +37 -14
- package/src/models/Organization.model.ts +27 -0
- package/src/models/Permission.model.ts +48 -0
- package/src/models/Project.model.ts +50 -0
- package/src/models/Settings.model.ts +31 -0
- package/src/models/{index.js → index.ts} +8 -8
- package/src/platform.ts +55 -0
- package/src/postgres.ts +309 -0
- package/src/routes/index.ts +8 -0
- package/src/routes/metrics.ts +13 -0
- package/src/routes/oauth.ts +267 -0
- package/src/routes/{organizations.js → organizations.ts} +10 -8
- package/src/routes/{permissions.js → permissions.ts} +8 -6
- package/src/routes/{projects.js → projects.ts} +22 -16
- package/src/routes/settings.ts +31 -0
- package/src/schemas/{Organization.js → Organization.ts} +2 -2
- package/src/schemas/{Permission.js → Permission.ts} +2 -2
- package/src/schemas/{Project.js → Project.ts} +2 -2
- package/src/schemas/index.ts +5 -0
- package/src/sequelize.ts +13 -0
- package/src/{test.js → test.ts} +11 -13
- package/src/types/Organization.ts +9 -0
- package/src/types/Permission.ts +13 -0
- package/src/types/Project.ts +14 -0
- package/src/types/index.ts +5 -0
- package/tsconfig.json +32 -0
- package/.eslintrc.js +0 -20
- package/index.js +0 -1
- package/src/config.js +0 -21
- package/src/dynamodb.js +0 -18
- package/src/express.js +0 -58
- package/src/lib/test.js +0 -69
- package/src/models/Organization.js +0 -17
- package/src/models/Permission.js +0 -33
- package/src/models/Project.js +0 -37
- package/src/models/Settings.js +0 -21
- package/src/openapi.js +0 -40
- package/src/platform.js +0 -56
- package/src/postgres.js +0 -308
- package/src/routes/index.js +0 -15
- package/src/routes/metrics.js +0 -12
- package/src/routes/oauth.js +0 -213
- package/src/routes/settings.js +0 -25
- package/src/schemas/index.js +0 -5
package/.eslintrc.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
env: {
|
|
3
|
+
es6: true,
|
|
4
|
+
node: true,
|
|
5
|
+
mocha: true,
|
|
6
|
+
},
|
|
7
|
+
parser: "@typescript-eslint/parser",
|
|
8
|
+
parserOptions: {
|
|
9
|
+
ecmaVersion: 2022,
|
|
10
|
+
sourceType: "module",
|
|
11
|
+
project: "./tsconfig.json",
|
|
12
|
+
},
|
|
13
|
+
plugins: ["@typescript-eslint"],
|
|
14
|
+
extends: [
|
|
15
|
+
"eslint:recommended",
|
|
16
|
+
"plugin:@typescript-eslint/recommended",
|
|
17
|
+
"plugin:prettier/recommended",
|
|
18
|
+
],
|
|
19
|
+
ignorePatterns: ["/node_modules", "/dist"],
|
|
20
|
+
rules: {
|
|
21
|
+
eqeqeq: ["error", "always"],
|
|
22
|
+
"no-console": "off",
|
|
23
|
+
"no-eval": "error",
|
|
24
|
+
"no-var": "error",
|
|
25
|
+
"prefer-arrow-callback": "error",
|
|
26
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
27
|
+
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
28
|
+
},
|
|
29
|
+
};
|
package/index.ts
ADDED
package/package.json
CHANGED
|
@@ -1,22 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canmingir/link-express",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "",
|
|
5
|
-
"main": "index.
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"types": "index.ts",
|
|
7
|
+
"typesVersions": {
|
|
8
|
+
"*": {
|
|
9
|
+
"models": [
|
|
10
|
+
"src/models/index.ts"
|
|
11
|
+
],
|
|
12
|
+
"test": [
|
|
13
|
+
"src/test.ts"
|
|
14
|
+
],
|
|
15
|
+
"authorization": [
|
|
16
|
+
"src/authorization.ts"
|
|
17
|
+
],
|
|
18
|
+
"error": [
|
|
19
|
+
"src/error.ts"
|
|
20
|
+
],
|
|
21
|
+
"types": [
|
|
22
|
+
"src/types/index.ts"
|
|
23
|
+
],
|
|
24
|
+
"sequelize": [
|
|
25
|
+
"src/sequelize.ts"
|
|
26
|
+
],
|
|
27
|
+
"event": [
|
|
28
|
+
"src/event/index.ts"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
6
32
|
"author": "NucTeam",
|
|
33
|
+
"bin": {
|
|
34
|
+
"event-listener": "./bin/event-listener.ts"
|
|
35
|
+
},
|
|
7
36
|
"exports": {
|
|
8
|
-
".": "./index.
|
|
9
|
-
"./models": "./src/models/index.
|
|
10
|
-
"./test": "./src/test.
|
|
11
|
-
"./authorization": "./src/authorization.
|
|
12
|
-
"./error": "./src/error.
|
|
37
|
+
".": "./index.ts",
|
|
38
|
+
"./models": "./src/models/index.ts",
|
|
39
|
+
"./test": "./src/test.ts",
|
|
40
|
+
"./authorization": "./src/authorization.ts",
|
|
41
|
+
"./error": "./src/error.ts",
|
|
42
|
+
"./types": "./src/types/index.ts",
|
|
43
|
+
"./sequelize": "./src/sequelize.ts",
|
|
44
|
+
"./event": "./src/event/serc/Event.ts",
|
|
45
|
+
"./event/client": "./src/event/client/index.ts",
|
|
46
|
+
"./event/server": "./src/event/server/server.ts"
|
|
13
47
|
},
|
|
14
48
|
"scripts": {
|
|
15
|
-
"prepare": "
|
|
49
|
+
"prepare": "tsx prepare.ts",
|
|
50
|
+
"build": "tsc",
|
|
16
51
|
"dev": "node server --dev",
|
|
17
52
|
"start": "node server",
|
|
18
53
|
"test": "echo 'No tests'",
|
|
19
|
-
"lint": "eslint . --ext .
|
|
54
|
+
"lint": "eslint . --ext .ts"
|
|
20
55
|
},
|
|
21
56
|
"dependencies": {
|
|
22
57
|
"@aws-sdk/client-dynamodb": "^3.614.0",
|
|
@@ -31,18 +66,35 @@
|
|
|
31
66
|
"joi": "^17.13.3",
|
|
32
67
|
"joi-to-swagger": "^6.2.0",
|
|
33
68
|
"jsonwebtoken": "^9.0.2",
|
|
69
|
+
"kafkajs": "^2.2.4",
|
|
34
70
|
"lodash": "^4.17.21",
|
|
35
71
|
"morgan": "^1.10.0",
|
|
72
|
+
"oracledb": "^6.10.0",
|
|
36
73
|
"pg": "^8.12.0",
|
|
37
74
|
"pino": "^10.1.0",
|
|
38
75
|
"pino-elasticsearch": "^8.1.0",
|
|
39
76
|
"prom-client": "^15.1.3",
|
|
40
|
-
"sequelize": "^6.37.
|
|
77
|
+
"sequelize": "^6.37.7",
|
|
78
|
+
"sequelize-typescript": "^2.1.6",
|
|
79
|
+
"socket.io": "^4.8.1",
|
|
80
|
+
"socket.io-client": "^4.8.1",
|
|
41
81
|
"swagger-jsdoc": "^6.2.8",
|
|
42
|
-
"
|
|
82
|
+
"ts-node": "^10.9.2",
|
|
43
83
|
"uuid": "^10.0.0"
|
|
44
84
|
},
|
|
45
85
|
"devDependencies": {
|
|
86
|
+
"@types/cors": "^2.8.19",
|
|
87
|
+
"@types/express": "^5.0.5",
|
|
88
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
89
|
+
"@types/lodash": "^4.17.21",
|
|
90
|
+
"@types/morgan": "^1.9.10",
|
|
91
|
+
"@types/node": "^24.10.1",
|
|
92
|
+
"@types/pg": "^8.15.6",
|
|
93
|
+
"@types/swagger-jsdoc": "^6.0.4",
|
|
94
|
+
"@types/swagger-ui-express": "^4.1.8",
|
|
95
|
+
"@types/uuid": "^10.0.0",
|
|
96
|
+
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
|
97
|
+
"@typescript-eslint/parser": "^8.48.0",
|
|
46
98
|
"axios-mock-adapter": "^1.21.1",
|
|
47
99
|
"eslint": "^8.57.1",
|
|
48
100
|
"eslint-config-prettier": "^8.5.0",
|
|
@@ -50,6 +102,8 @@
|
|
|
50
102
|
"mocha": "10.8.2",
|
|
51
103
|
"prettier": "^2.6.2",
|
|
52
104
|
"sqlite3": "^5.1.6",
|
|
53
|
-
"supertest": "^7.0.0"
|
|
105
|
+
"supertest": "^7.0.0",
|
|
106
|
+
"tsx": "^4.20.6",
|
|
107
|
+
"typescript": "^5.9.3"
|
|
54
108
|
}
|
|
55
109
|
}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import { Request, Response, NextFunction } from "express";
|
|
3
|
+
import { AuthorizationError } from "./error";
|
|
4
|
+
|
|
5
|
+
interface Session {
|
|
6
|
+
projectId: string;
|
|
7
|
+
userId: string;
|
|
8
|
+
roles: string[];
|
|
9
|
+
appId: string;
|
|
10
|
+
organizationId: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare module "express-serve-static-core" {
|
|
14
|
+
interface Request {
|
|
15
|
+
session: Session;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function verify(req: Request, _res: Response, next: NextFunction): void {
|
|
4
20
|
if (process.env.PROFILE === "TEST") {
|
|
5
21
|
switch (process.env.PROJECT_ID) {
|
|
6
22
|
case "0c756054-2d28-4f87-9b12-8023a79136a5":
|
|
@@ -69,8 +85,15 @@ function verify(req, res, next) {
|
|
|
69
85
|
try {
|
|
70
86
|
const { sub, aud, rls, aid, oid } = jwt.verify(
|
|
71
87
|
token,
|
|
72
|
-
process.env.JWT_SECRET
|
|
73
|
-
)
|
|
88
|
+
process.env.JWT_SECRET as string
|
|
89
|
+
) as {
|
|
90
|
+
sub: string;
|
|
91
|
+
aud: string;
|
|
92
|
+
rls: string[];
|
|
93
|
+
aid: string;
|
|
94
|
+
oid: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
74
97
|
req.session = {
|
|
75
98
|
projectId: aud,
|
|
76
99
|
userId: sub,
|
|
@@ -85,9 +108,8 @@ function verify(req, res, next) {
|
|
|
85
108
|
next();
|
|
86
109
|
}
|
|
87
110
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return (req, res, next) => {
|
|
111
|
+
function authorize(_role: string) {
|
|
112
|
+
return (req: Request, _res: Response, next: NextFunction): void => {
|
|
91
113
|
const { roles } = req.session;
|
|
92
114
|
|
|
93
115
|
// TODO Add expression check for role
|
|
@@ -99,4 +121,4 @@ function authorize(role) {
|
|
|
99
121
|
};
|
|
100
122
|
}
|
|
101
123
|
|
|
102
|
-
|
|
124
|
+
export { verify, authorize };
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
|
|
3
|
+
interface LoggerConfig {
|
|
4
|
+
elasticsearch: {
|
|
5
|
+
index: string;
|
|
6
|
+
consistency?: string;
|
|
7
|
+
node: string;
|
|
8
|
+
esVersion?: number;
|
|
9
|
+
flushBytes?: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ProjectConfig {
|
|
14
|
+
name: string;
|
|
15
|
+
version: string;
|
|
16
|
+
oauth?: {
|
|
17
|
+
jwt: {
|
|
18
|
+
identifier: string;
|
|
19
|
+
};
|
|
20
|
+
providers: Record<
|
|
21
|
+
string,
|
|
22
|
+
{
|
|
23
|
+
tokenUrl: string;
|
|
24
|
+
userUrl: string;
|
|
25
|
+
clientId: string;
|
|
26
|
+
redirectUri?: string;
|
|
27
|
+
userIdentifier: string;
|
|
28
|
+
userFields: {
|
|
29
|
+
name: string;
|
|
30
|
+
displayName: string;
|
|
31
|
+
avatarUrl: string;
|
|
32
|
+
email: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface LinkConfig {
|
|
40
|
+
[key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface OpenapiConfig {
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface PostgresConfig {
|
|
48
|
+
uri: string;
|
|
49
|
+
debug?: boolean;
|
|
50
|
+
sync?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface DynamodbConfig {
|
|
54
|
+
region: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface PushGatewayConfig {
|
|
58
|
+
url?: string;
|
|
59
|
+
jobName?: string;
|
|
60
|
+
instance?: string;
|
|
61
|
+
interval?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface MetricsConfig {
|
|
65
|
+
enabled: boolean;
|
|
66
|
+
url?: string;
|
|
67
|
+
pushGateway: PushGatewayConfig;
|
|
68
|
+
interval?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface Config {
|
|
72
|
+
link: LinkConfig;
|
|
73
|
+
project: ProjectConfig | null;
|
|
74
|
+
openapi: OpenapiConfig;
|
|
75
|
+
postgres: PostgresConfig | null;
|
|
76
|
+
dynamodb: DynamodbConfig | null;
|
|
77
|
+
pushGateway: PushGatewayConfig;
|
|
78
|
+
title?: string;
|
|
79
|
+
version?: string;
|
|
80
|
+
logger?: LoggerConfig;
|
|
81
|
+
metrics?: MetricsConfig;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let _config: Config = {
|
|
85
|
+
link: {},
|
|
86
|
+
project: null,
|
|
87
|
+
openapi: {},
|
|
88
|
+
postgres: null,
|
|
89
|
+
dynamodb: null,
|
|
90
|
+
pushGateway: {},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function init(config: Partial<Config> = {}): Config {
|
|
94
|
+
_config = _.merge(
|
|
95
|
+
{
|
|
96
|
+
link: {},
|
|
97
|
+
project: null,
|
|
98
|
+
openapi: {},
|
|
99
|
+
postgres: null,
|
|
100
|
+
dynamodb: null,
|
|
101
|
+
pushGateway: {},
|
|
102
|
+
},
|
|
103
|
+
config
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return _config;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const getConfig = (): Config => _config;
|
|
110
|
+
|
|
111
|
+
export { getConfig, init, Config };
|
|
112
|
+
export default getConfig;
|
package/src/dynamodb.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
2
|
+
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
|
|
3
|
+
import { getConfig } from "./config";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
const { dynamodb } = getConfig();
|
|
9
|
+
|
|
10
|
+
if (!dynamodb) {
|
|
11
|
+
throw new Error("DynamoDB configuration is required");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const client = new DynamoDBClient({
|
|
15
|
+
region: dynamodb.region,
|
|
16
|
+
credentials: {
|
|
17
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
|
|
18
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const docClient = DynamoDBDocumentClient.from(client);
|
|
23
|
+
|
|
24
|
+
export { docClient };
|
|
@@ -1,7 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
import { ValidationError } from "joi";
|
|
2
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
interface AxiosError extends Error {
|
|
5
|
+
isAxiosError: boolean;
|
|
6
|
+
response?: {
|
|
7
|
+
status: number;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ErrorWithError {
|
|
12
|
+
error: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const handle = (
|
|
16
|
+
err:
|
|
17
|
+
| string
|
|
18
|
+
| ValidationError
|
|
19
|
+
| AuthorizationError
|
|
20
|
+
| AuthenticationError
|
|
21
|
+
| NotFoundError
|
|
22
|
+
| AxiosError
|
|
23
|
+
| ErrorWithError
|
|
24
|
+
| Error,
|
|
25
|
+
_req: Request,
|
|
26
|
+
res: Response,
|
|
27
|
+
_next: NextFunction
|
|
28
|
+
): Response => {
|
|
5
29
|
if (typeof err === "string") {
|
|
6
30
|
return res.status(400).json({ error: err });
|
|
7
31
|
}
|
|
@@ -22,11 +46,11 @@ const handle = (err, req, res, next) => {
|
|
|
22
46
|
return res.status(404).end();
|
|
23
47
|
}
|
|
24
48
|
|
|
25
|
-
if (err.isAxiosError) {
|
|
49
|
+
if ("isAxiosError" in err && err.isAxiosError) {
|
|
26
50
|
return res.status(err.response?.status || 503).end();
|
|
27
51
|
}
|
|
28
52
|
|
|
29
|
-
if (err.error) {
|
|
53
|
+
if ("error" in err && err.error) {
|
|
30
54
|
return res.status(400).json(err);
|
|
31
55
|
} else {
|
|
32
56
|
console.error(err);
|
|
@@ -38,9 +62,4 @@ class AuthorizationError extends Error {}
|
|
|
38
62
|
class AuthenticationError extends Error {}
|
|
39
63
|
class NotFoundError extends Error {}
|
|
40
64
|
|
|
41
|
-
|
|
42
|
-
handle,
|
|
43
|
-
AuthorizationError,
|
|
44
|
-
NotFoundError,
|
|
45
|
-
AuthenticationError,
|
|
46
|
-
};
|
|
65
|
+
export { handle, AuthorizationError, NotFoundError, AuthenticationError };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Consumer, Kafka, Producer } from "kafkajs";
|
|
2
|
+
|
|
3
|
+
import { EventAdapter } from "../types/types";
|
|
4
|
+
|
|
5
|
+
export class KafkaAdapter implements EventAdapter {
|
|
6
|
+
private kafka: Kafka;
|
|
7
|
+
private consumer: Consumer | null = null;
|
|
8
|
+
private producer: Producer | null = null;
|
|
9
|
+
private messageHandler?: (type: string, payload: object) => void;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
private readonly options: {
|
|
13
|
+
clientId: string;
|
|
14
|
+
brokers: string[];
|
|
15
|
+
groupId: string;
|
|
16
|
+
topics: string[];
|
|
17
|
+
}
|
|
18
|
+
) {
|
|
19
|
+
this.kafka = new Kafka({
|
|
20
|
+
clientId: options.clientId,
|
|
21
|
+
brokers: options.brokers,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async connect(): Promise<void> {
|
|
26
|
+
this.producer = this.kafka.producer();
|
|
27
|
+
await this.producer.connect();
|
|
28
|
+
this.consumer = this.kafka.consumer({ groupId: this.options.groupId });
|
|
29
|
+
|
|
30
|
+
await this.consumer.connect();
|
|
31
|
+
await this.consumer.subscribe({
|
|
32
|
+
topics: [/^(?!__).*$/],
|
|
33
|
+
fromBeginning: false,
|
|
34
|
+
});
|
|
35
|
+
await this.consumer.run({
|
|
36
|
+
partitionsConsumedConcurrently: 160,
|
|
37
|
+
eachMessage: async ({ topic, message }) => {
|
|
38
|
+
if (topic.startsWith("__")) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (this.messageHandler) {
|
|
43
|
+
try {
|
|
44
|
+
const payload = JSON.parse(message.value?.toString() || "{}");
|
|
45
|
+
this.messageHandler(topic, payload);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(
|
|
48
|
+
`Error processing message for topic ${topic}:`,
|
|
49
|
+
error
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
console.log(`Kafka consumer connected`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async disconnect(): Promise<void> {
|
|
59
|
+
if (this.consumer) {
|
|
60
|
+
await this.consumer.stop();
|
|
61
|
+
await this.consumer.disconnect();
|
|
62
|
+
this.consumer = null;
|
|
63
|
+
}
|
|
64
|
+
if (this.producer) {
|
|
65
|
+
await this.producer.disconnect();
|
|
66
|
+
this.producer = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async publish<T = object>(type: string, payload: T): Promise<void> {
|
|
71
|
+
if (!this.producer) {
|
|
72
|
+
throw new Error("Producer not connected");
|
|
73
|
+
}
|
|
74
|
+
this.producer.send({
|
|
75
|
+
topic: type,
|
|
76
|
+
messages: [{ value: JSON.stringify(payload) }],
|
|
77
|
+
}).then(() => {
|
|
78
|
+
console.log(`Message published to topic ${type}`);
|
|
79
|
+
}).catch((error) => {
|
|
80
|
+
console.error(`Error publishing message to topic ${type}:`, error);
|
|
81
|
+
return Promise.reject(error);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async subscribe(type: string): Promise<void> {
|
|
86
|
+
// No-op: EventManager handles callback registration in memory
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async unsubscribe(type: string): Promise<void> {
|
|
90
|
+
// No-op: EventManager handles callback removal in memory
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onMessage(handler: (type: string, payload: object) => void): void {
|
|
94
|
+
this.messageHandler = handler;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getBacklog(topics: string[]): Promise<Map<string, number>> {
|
|
98
|
+
const backlogMap = new Map<string, number>();
|
|
99
|
+
|
|
100
|
+
if (topics.length === 0) {
|
|
101
|
+
return backlogMap;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const admin = this.kafka.admin();
|
|
105
|
+
await admin.connect();
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
for (const topic of topics) {
|
|
109
|
+
const offsetsResponse = await admin.fetchOffsets({
|
|
110
|
+
groupId: this.options.groupId,
|
|
111
|
+
topics: [topic],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const topicOffsets = await admin.fetchTopicOffsets(topic);
|
|
115
|
+
let totalLag = 0;
|
|
116
|
+
|
|
117
|
+
const topicResponse = offsetsResponse.find((r) => r.topic === topic);
|
|
118
|
+
if (topicResponse) {
|
|
119
|
+
topicResponse.partitions.forEach((partitionOffset) => {
|
|
120
|
+
const latestOffset = topicOffsets.find(
|
|
121
|
+
(to) => to.partition === partitionOffset.partition
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (latestOffset) {
|
|
125
|
+
const consumerOffset = parseInt(partitionOffset.offset);
|
|
126
|
+
const latestOffsetValue = parseInt(latestOffset.offset);
|
|
127
|
+
totalLag += Math.max(0, latestOffsetValue - consumerOffset);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
backlogMap.set(topic, totalLag);
|
|
133
|
+
}
|
|
134
|
+
} finally {
|
|
135
|
+
await admin.disconnect();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return backlogMap;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Socket, io } from "socket.io-client";
|
|
2
|
+
|
|
3
|
+
import { EventAdapter } from "../types/types";
|
|
4
|
+
|
|
5
|
+
export class SocketAdapter implements EventAdapter {
|
|
6
|
+
private socket: Socket | null = null;
|
|
7
|
+
private messageHandler?: (type: string, payload: object) => void;
|
|
8
|
+
|
|
9
|
+
constructor(private readonly options: {
|
|
10
|
+
host: string;
|
|
11
|
+
port?: number;
|
|
12
|
+
protocol: string;
|
|
13
|
+
}) {}
|
|
14
|
+
|
|
15
|
+
async connect(): Promise<void> {
|
|
16
|
+
const { host, port, protocol } = this.options;
|
|
17
|
+
const socketPath = port ? `${protocol}://${host}:${port}` : `${protocol}://${host}`;
|
|
18
|
+
|
|
19
|
+
this.socket = io(socketPath);
|
|
20
|
+
|
|
21
|
+
this.socket.on("event", ({ type, payload }: { type: string; payload: object }) => {
|
|
22
|
+
if (this.messageHandler) {
|
|
23
|
+
this.messageHandler(type, payload);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async disconnect(): Promise<void> {
|
|
29
|
+
if (this.socket) {
|
|
30
|
+
this.socket.disconnect();
|
|
31
|
+
this.socket = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async publish(type: string, payload: object): Promise<void> {
|
|
36
|
+
if (!this.socket) {
|
|
37
|
+
throw new Error("Socket not connected");
|
|
38
|
+
}
|
|
39
|
+
this.socket.emit("publish", { type, payload });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async subscribe(type: string): Promise<void> {
|
|
43
|
+
if (!this.socket) {
|
|
44
|
+
throw new Error("Socket not connected");
|
|
45
|
+
}
|
|
46
|
+
this.socket.emit("subscribe", type);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async unsubscribe(type: string): Promise<void> {
|
|
50
|
+
if (!this.socket) {
|
|
51
|
+
throw new Error("Socket not connected");
|
|
52
|
+
}
|
|
53
|
+
this.socket.emit("unsubscribe", type);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onMessage(handler: (type: string, payload: object) => void): void {
|
|
57
|
+
this.messageHandler = handler;
|
|
58
|
+
}
|
|
59
|
+
}
|