@omnitronix/game-engine-sdk 1.0.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/dist/bootstrap/index.d.ts +15 -0
- package/dist/bootstrap/index.d.ts.map +1 -0
- package/dist/bootstrap/index.js +156 -0
- package/dist/common/api-docs/setup-documentation.d.ts +3 -0
- package/dist/common/api-docs/setup-documentation.d.ts.map +1 -0
- package/dist/common/api-docs/setup-documentation.js +27 -0
- package/dist/common/application-bootstrap-options.interface.d.ts +4 -0
- package/dist/common/application-bootstrap-options.interface.d.ts.map +1 -0
- package/dist/common/application-bootstrap-options.interface.js +2 -0
- package/dist/common/error-handling/all-exceptions.filter.d.ts +22 -0
- package/dist/common/error-handling/all-exceptions.filter.d.ts.map +1 -0
- package/dist/common/error-handling/all-exceptions.filter.js +131 -0
- package/dist/common/error-handling/domain.exception.d.ts +8 -0
- package/dist/common/error-handling/domain.exception.d.ts.map +1 -0
- package/dist/common/error-handling/domain.exception.js +14 -0
- package/dist/common/error-handling/error-code-mapper.d.ts +6 -0
- package/dist/common/error-handling/error-code-mapper.d.ts.map +1 -0
- package/dist/common/error-handling/error-code-mapper.js +93 -0
- package/dist/common/error-handling/internal-error-code.d.ts +67 -0
- package/dist/common/error-handling/internal-error-code.d.ts.map +1 -0
- package/dist/common/error-handling/internal-error-code.js +80 -0
- package/dist/common/index.d.ts +21 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/index.js +51 -0
- package/dist/common/logger/logger.d.ts +24 -0
- package/dist/common/logger/logger.d.ts.map +1 -0
- package/dist/common/logger/logger.js +137 -0
- package/dist/common/logger/logger.module.d.ts +3 -0
- package/dist/common/logger/logger.module.d.ts.map +1 -0
- package/dist/common/logger/logger.module.js +19 -0
- package/dist/common/logger/logging.middleware.d.ts +8 -0
- package/dist/common/logger/logging.middleware.d.ts.map +1 -0
- package/dist/common/logger/logging.middleware.js +44 -0
- package/dist/common/metrics/database-metrics.service.d.ts +8 -0
- package/dist/common/metrics/database-metrics.service.d.ts.map +1 -0
- package/dist/common/metrics/database-metrics.service.js +39 -0
- package/dist/common/metrics/helpers/metrics.helpers.d.ts +5 -0
- package/dist/common/metrics/helpers/metrics.helpers.d.ts.map +1 -0
- package/dist/common/metrics/helpers/metrics.helpers.js +38 -0
- package/dist/common/metrics/metrics.module.d.ts +3 -0
- package/dist/common/metrics/metrics.module.d.ts.map +1 -0
- package/dist/common/metrics/metrics.module.js +207 -0
- package/dist/common/metrics/metrics.port.d.ts +22 -0
- package/dist/common/metrics/metrics.port.d.ts.map +1 -0
- package/dist/common/metrics/metrics.port.js +4 -0
- package/dist/common/metrics/prometheus.service.d.ts +25 -0
- package/dist/common/metrics/prometheus.service.d.ts.map +1 -0
- package/dist/common/metrics/prometheus.service.js +105 -0
- package/dist/common/retry/retry-policies.d.ts +8 -0
- package/dist/common/retry/retry-policies.d.ts.map +1 -0
- package/dist/common/retry/retry-policies.js +43 -0
- package/dist/common/retry/retry-policy.d.ts +25 -0
- package/dist/common/retry/retry-policy.d.ts.map +1 -0
- package/dist/common/retry/retry-policy.js +65 -0
- package/dist/common/secrets-provider/secrets-provider.aws.d.ts +13 -0
- package/dist/common/secrets-provider/secrets-provider.aws.d.ts.map +1 -0
- package/dist/common/secrets-provider/secrets-provider.aws.js +72 -0
- package/dist/common/secrets-provider/secrets-provider.d.ts +5 -0
- package/dist/common/secrets-provider/secrets-provider.d.ts.map +1 -0
- package/dist/common/secrets-provider/secrets-provider.js +4 -0
- package/dist/common/secrets-provider/secrets-provider.local.d.ts +8 -0
- package/dist/common/secrets-provider/secrets-provider.local.d.ts.map +1 -0
- package/dist/common/secrets-provider/secrets-provider.local.js +75 -0
- package/dist/common/secrets-provider/secrets-provider.module.d.ts +5 -0
- package/dist/common/secrets-provider/secrets-provider.module.d.ts.map +1 -0
- package/dist/common/secrets-provider/secrets-provider.module.js +47 -0
- package/dist/esm/bootstrap/index.js +120 -0
- package/dist/esm/common/api-docs/setup-documentation.js +23 -0
- package/dist/esm/common/application-bootstrap-options.interface.js +1 -0
- package/dist/esm/common/error-handling/all-exceptions.filter.js +128 -0
- package/dist/esm/common/error-handling/domain.exception.js +10 -0
- package/dist/esm/common/error-handling/error-code-mapper.js +89 -0
- package/dist/esm/common/error-handling/internal-error-code.js +77 -0
- package/dist/esm/common/index.js +25 -0
- package/dist/esm/common/logger/logger.js +133 -0
- package/dist/esm/common/logger/logger.module.js +16 -0
- package/dist/esm/common/logger/logging.middleware.js +41 -0
- package/dist/esm/common/metrics/database-metrics.service.js +36 -0
- package/dist/esm/common/metrics/helpers/metrics.helpers.js +32 -0
- package/dist/esm/common/metrics/metrics.module.js +204 -0
- package/dist/esm/common/metrics/metrics.port.js +1 -0
- package/dist/esm/common/metrics/prometheus.service.js +102 -0
- package/dist/esm/common/retry/retry-policies.js +39 -0
- package/dist/esm/common/retry/retry-policy.js +61 -0
- package/dist/esm/common/secrets-provider/secrets-provider.aws.js +69 -0
- package/dist/esm/common/secrets-provider/secrets-provider.js +1 -0
- package/dist/esm/common/secrets-provider/secrets-provider.local.js +39 -0
- package/dist/esm/common/secrets-provider/secrets-provider.module.js +44 -0
- package/dist/esm/generated/game-engine-registry_pb.js +28 -0
- package/dist/esm/generated/game-engine_pb.js +57 -0
- package/dist/esm/grpc/connect-router.middleware.js +16 -0
- package/dist/esm/grpc/game-engine.grpc.in-adapter.js +109 -0
- package/dist/esm/grpc/index.js +2 -0
- package/dist/esm/health/application/ports/in/health-in.port.js +1 -0
- package/dist/esm/health/application/ports/out/database-health.out-port.js +1 -0
- package/dist/esm/health/application/ports/out/shutdown.out-port.js +1 -0
- package/dist/esm/health/application/ports/out/system-health.out-port.js +1 -0
- package/dist/esm/health/application/services/health.service.js +81 -0
- package/dist/esm/health/domain/health.js +7 -0
- package/dist/esm/health/health.module.js +65 -0
- package/dist/esm/health/index.js +15 -0
- package/dist/esm/health/infrastructure/adapters/in/health.http.in-adapter.js +96 -0
- package/dist/esm/health/infrastructure/adapters/out/shutdown/shutdown.out-adapter.js +45 -0
- package/dist/esm/health/infrastructure/adapters/out/system/system-health.out-adapter.js +43 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/registration/adapters/game-engine-registry.grpc.out-adapter.js +59 -0
- package/dist/esm/registration/index.js +3 -0
- package/dist/esm/registration/ports/game-engine-registry.out-port.js +1 -0
- package/dist/esm/registration/services/game-engine-auto-register.service.js +78 -0
- package/dist/esm/types.js +8 -0
- package/dist/generated/game-engine-registry_pb.d.ts +76 -0
- package/dist/generated/game-engine-registry_pb.d.ts.map +1 -0
- package/dist/generated/game-engine-registry_pb.js +31 -0
- package/dist/generated/game-engine_pb.d.ts +171 -0
- package/dist/generated/game-engine_pb.d.ts.map +1 -0
- package/dist/generated/game-engine_pb.js +60 -0
- package/dist/grpc/connect-router.middleware.d.ts +7 -0
- package/dist/grpc/connect-router.middleware.d.ts.map +1 -0
- package/dist/grpc/connect-router.middleware.js +19 -0
- package/dist/grpc/game-engine.grpc.in-adapter.d.ts +21 -0
- package/dist/grpc/game-engine.grpc.in-adapter.d.ts.map +1 -0
- package/dist/grpc/game-engine.grpc.in-adapter.js +112 -0
- package/dist/grpc/index.d.ts +3 -0
- package/dist/grpc/index.d.ts.map +1 -0
- package/dist/grpc/index.js +7 -0
- package/dist/health/application/ports/in/health-in.port.d.ts +7 -0
- package/dist/health/application/ports/in/health-in.port.d.ts.map +1 -0
- package/dist/health/application/ports/in/health-in.port.js +4 -0
- package/dist/health/application/ports/out/database-health.out-port.d.ts +6 -0
- package/dist/health/application/ports/out/database-health.out-port.d.ts.map +1 -0
- package/dist/health/application/ports/out/database-health.out-port.js +4 -0
- package/dist/health/application/ports/out/shutdown.out-port.d.ts +6 -0
- package/dist/health/application/ports/out/shutdown.out-port.d.ts.map +1 -0
- package/dist/health/application/ports/out/shutdown.out-port.js +4 -0
- package/dist/health/application/ports/out/system-health.out-port.d.ts +7 -0
- package/dist/health/application/ports/out/system-health.out-port.d.ts.map +1 -0
- package/dist/health/application/ports/out/system-health.out-port.js +4 -0
- package/dist/health/application/services/health.service.d.ts +13 -0
- package/dist/health/application/services/health.service.d.ts.map +1 -0
- package/dist/health/application/services/health.service.js +84 -0
- package/dist/health/domain/health.d.ts +15 -0
- package/dist/health/domain/health.d.ts.map +1 -0
- package/dist/health/domain/health.js +11 -0
- package/dist/health/health.module.d.ts +10 -0
- package/dist/health/health.module.d.ts.map +1 -0
- package/dist/health/health.module.js +68 -0
- package/dist/health/index.d.ts +11 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +28 -0
- package/dist/health/infrastructure/adapters/in/health.http.in-adapter.d.ts +16 -0
- package/dist/health/infrastructure/adapters/in/health.http.in-adapter.d.ts.map +1 -0
- package/dist/health/infrastructure/adapters/in/health.http.in-adapter.js +99 -0
- package/dist/health/infrastructure/adapters/out/shutdown/shutdown.out-adapter.d.ts +13 -0
- package/dist/health/infrastructure/adapters/out/shutdown/shutdown.out-adapter.d.ts.map +1 -0
- package/dist/health/infrastructure/adapters/out/shutdown/shutdown.out-adapter.js +48 -0
- package/dist/health/infrastructure/adapters/out/system/system-health.out-adapter.d.ts +12 -0
- package/dist/health/infrastructure/adapters/out/system/system-health.out-adapter.d.ts.map +1 -0
- package/dist/health/infrastructure/adapters/out/system/system-health.out-adapter.js +46 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/registration/adapters/game-engine-registry.grpc.out-adapter.d.ts +12 -0
- package/dist/registration/adapters/game-engine-registry.grpc.out-adapter.d.ts.map +1 -0
- package/dist/registration/adapters/game-engine-registry.grpc.out-adapter.js +62 -0
- package/dist/registration/index.d.ts +4 -0
- package/dist/registration/index.d.ts.map +1 -0
- package/dist/registration/index.js +9 -0
- package/dist/registration/ports/game-engine-registry.out-port.d.ts +9 -0
- package/dist/registration/ports/game-engine-registry.out-port.d.ts.map +1 -0
- package/dist/registration/ports/game-engine-registry.out-port.js +4 -0
- package/dist/registration/services/game-engine-auto-register.service.d.ts +14 -0
- package/dist/registration/services/game-engine-auto-register.service.d.ts.map +1 -0
- package/dist/registration/services/game-engine-auto-register.service.js +81 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/package.json +159 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { DomainException } from '../error-handling/domain.exception';
|
|
2
|
+
import { createLogger, format, transports } from 'winston';
|
|
3
|
+
export class Logger {
|
|
4
|
+
constructor(context, logFormat) {
|
|
5
|
+
this.context = context;
|
|
6
|
+
const format = logFormat ?? this.detectLogFormat();
|
|
7
|
+
this.logger = this.createWinstonLogger(format);
|
|
8
|
+
}
|
|
9
|
+
detectLogFormat() {
|
|
10
|
+
const envFormat = process.env.LOG_FORMAT?.toLowerCase();
|
|
11
|
+
switch (envFormat) {
|
|
12
|
+
case 'pretty':
|
|
13
|
+
return 'pretty';
|
|
14
|
+
case 'json':
|
|
15
|
+
return 'json';
|
|
16
|
+
default:
|
|
17
|
+
return 'json';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
createWinstonLogger(logFormat) {
|
|
21
|
+
const winstonFormat = this.getLogFormat(logFormat);
|
|
22
|
+
return createLogger({
|
|
23
|
+
level: 'info',
|
|
24
|
+
format: winstonFormat,
|
|
25
|
+
transports: this.getTransports(),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
getLogFormat(logFormat) {
|
|
29
|
+
return format.combine(format.timestamp(), this.selectFormat(logFormat));
|
|
30
|
+
}
|
|
31
|
+
selectFormat(logFormat) {
|
|
32
|
+
switch (logFormat) {
|
|
33
|
+
case 'pretty':
|
|
34
|
+
return this.getPrettyFormat();
|
|
35
|
+
case 'json':
|
|
36
|
+
return this.getJsonFormat();
|
|
37
|
+
default:
|
|
38
|
+
return this.getJsonFormat();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
getJsonFormat() {
|
|
42
|
+
return format.combine(format(info => {
|
|
43
|
+
if (this.context) {
|
|
44
|
+
info.message = `[${this.context}] ${info.message}`;
|
|
45
|
+
}
|
|
46
|
+
return info;
|
|
47
|
+
})(), format.json());
|
|
48
|
+
}
|
|
49
|
+
getPrettyFormat() {
|
|
50
|
+
return format.combine(format.colorize(), format.printf(({ timestamp, level, message, error, ...meta }) => {
|
|
51
|
+
const contextStr = this.context ? `[${this.context}] ` : '';
|
|
52
|
+
let output = `${timestamp} ${level}: ${contextStr}${message}`;
|
|
53
|
+
if (error && typeof error === 'object' && 'name' in error && 'message' in error) {
|
|
54
|
+
output += `\n Error: ${error.name}: ${error.message}`;
|
|
55
|
+
if (error.name === 'DomainException' && 'errorCode' in error) {
|
|
56
|
+
output += `\n Internal Code: ${error.errorCode}`;
|
|
57
|
+
}
|
|
58
|
+
if ('stack' in error && error.stack) {
|
|
59
|
+
output += `\n Stack: ${error.stack}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const metaKeys = Object.keys(meta).filter(key => key !== 'timestamp' && key !== 'level' && key !== 'message');
|
|
63
|
+
if (metaKeys.length > 0) {
|
|
64
|
+
output += `\n Meta: ${JSON.stringify(meta, null, 2)}`;
|
|
65
|
+
}
|
|
66
|
+
return output;
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
safeStringify(obj) {
|
|
70
|
+
try {
|
|
71
|
+
return JSON.stringify(obj, (key, value) => {
|
|
72
|
+
if (typeof value === 'bigint') {
|
|
73
|
+
return value.toString();
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (_error) {
|
|
79
|
+
return '[Object cannot be serialized]';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
getTransports() {
|
|
83
|
+
return [new transports.Console(), new transports.File({ filename: 'logs/app.log' })];
|
|
84
|
+
}
|
|
85
|
+
log(message, { level = 'info', ...rest } = {}) {
|
|
86
|
+
switch (level) {
|
|
87
|
+
case 'error':
|
|
88
|
+
this.logger.error(message, rest);
|
|
89
|
+
break;
|
|
90
|
+
case 'warn':
|
|
91
|
+
this.logger.warn(message, rest);
|
|
92
|
+
break;
|
|
93
|
+
case 'debug':
|
|
94
|
+
this.logger.debug(message, rest);
|
|
95
|
+
break;
|
|
96
|
+
case 'verbose':
|
|
97
|
+
this.logger.verbose(message, rest);
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
this.logger.info(message, rest);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
error(message, ...optionalParams) {
|
|
105
|
+
if (optionalParams.length > 0 && optionalParams[0] instanceof Error) {
|
|
106
|
+
const error = optionalParams[0];
|
|
107
|
+
const errorData = {
|
|
108
|
+
name: error.name,
|
|
109
|
+
message: error.message,
|
|
110
|
+
stack: error.stack,
|
|
111
|
+
};
|
|
112
|
+
if (error instanceof DomainException) {
|
|
113
|
+
errorData.errorCode = error.errorCode;
|
|
114
|
+
}
|
|
115
|
+
this.logger.error(message, {
|
|
116
|
+
error: errorData,
|
|
117
|
+
...optionalParams.slice(1),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.logger.error(message, ...optionalParams);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
warn(message, ...optionalParams) {
|
|
125
|
+
this.logger.warn(message, ...optionalParams);
|
|
126
|
+
}
|
|
127
|
+
debug(message, ...optionalParams) {
|
|
128
|
+
this.logger.debug(message, ...optionalParams);
|
|
129
|
+
}
|
|
130
|
+
verbose(message, ...optionalParams) {
|
|
131
|
+
this.logger.verbose(message, ...optionalParams);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Module } from '@nestjs/common';
|
|
8
|
+
import { ConfigModule } from '@nestjs/config';
|
|
9
|
+
let LoggerModule = class LoggerModule {
|
|
10
|
+
};
|
|
11
|
+
LoggerModule = __decorate([
|
|
12
|
+
Module({
|
|
13
|
+
imports: [ConfigModule],
|
|
14
|
+
})
|
|
15
|
+
], LoggerModule);
|
|
16
|
+
export { LoggerModule };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var LoggingMiddleware_1;
|
|
11
|
+
import { Injectable } from '@nestjs/common';
|
|
12
|
+
import { Logger } from './logger';
|
|
13
|
+
const BLOCKED_ENDPOINTS = ['/api/races/active'];
|
|
14
|
+
let LoggingMiddleware = LoggingMiddleware_1 = class LoggingMiddleware {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.logger = new Logger(LoggingMiddleware_1.name);
|
|
17
|
+
}
|
|
18
|
+
use(req, res, next) {
|
|
19
|
+
const { method, originalUrl, body } = req;
|
|
20
|
+
const sanitizedBody = { ...body };
|
|
21
|
+
if (sanitizedBody.password) {
|
|
22
|
+
sanitizedBody.password = '***';
|
|
23
|
+
}
|
|
24
|
+
if (sanitizedBody.token) {
|
|
25
|
+
sanitizedBody.token = '***';
|
|
26
|
+
}
|
|
27
|
+
res.on('finish', () => {
|
|
28
|
+
const isBlockedEndpoint = BLOCKED_ENDPOINTS.some(endpoint => originalUrl.includes(endpoint));
|
|
29
|
+
const isSuccessfulRequest = res.statusCode < 400;
|
|
30
|
+
if (!(isBlockedEndpoint && isSuccessfulRequest)) {
|
|
31
|
+
this.logger.log(`Request: ${method} ${originalUrl} | Body: ${JSON.stringify(sanitizedBody)}`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
next();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
LoggingMiddleware = LoggingMiddleware_1 = __decorate([
|
|
38
|
+
Injectable(),
|
|
39
|
+
__metadata("design:paramtypes", [])
|
|
40
|
+
], LoggingMiddleware);
|
|
41
|
+
export { LoggingMiddleware };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { METRICS_PORT } from './metrics.port';
|
|
14
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
15
|
+
let DatabaseMetricsService = class DatabaseMetricsService {
|
|
16
|
+
constructor(metricsPort) {
|
|
17
|
+
this.metricsPort = metricsPort;
|
|
18
|
+
}
|
|
19
|
+
updateConnectionPoolSize(poolType, state, size) {
|
|
20
|
+
this.metricsPort.getGauge('rgs_db_connection_pool_size').set({
|
|
21
|
+
pool_type: poolType,
|
|
22
|
+
state,
|
|
23
|
+
}, size);
|
|
24
|
+
}
|
|
25
|
+
trackConnectionError(errorType) {
|
|
26
|
+
this.metricsPort.getCounter('rgs_db_connection_errors_total').inc({
|
|
27
|
+
error_type: errorType,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
DatabaseMetricsService = __decorate([
|
|
32
|
+
Injectable(),
|
|
33
|
+
__param(0, Inject(METRICS_PORT)),
|
|
34
|
+
__metadata("design:paramtypes", [Object])
|
|
35
|
+
], DatabaseMetricsService);
|
|
36
|
+
export { DatabaseMetricsService };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function toMinorUnits(amount) {
|
|
2
|
+
return Math.round(amount * 100);
|
|
3
|
+
}
|
|
4
|
+
export function getEventCountBucket(count) {
|
|
5
|
+
if (count <= 5)
|
|
6
|
+
return '1-5';
|
|
7
|
+
if (count <= 10)
|
|
8
|
+
return '6-10';
|
|
9
|
+
if (count <= 25)
|
|
10
|
+
return '11-25';
|
|
11
|
+
if (count <= 50)
|
|
12
|
+
return '26-50';
|
|
13
|
+
if (count <= 100)
|
|
14
|
+
return '51-100';
|
|
15
|
+
return '100+';
|
|
16
|
+
}
|
|
17
|
+
export function getStreamVersionBucket(version) {
|
|
18
|
+
if (version <= 10)
|
|
19
|
+
return '1-10';
|
|
20
|
+
if (version <= 50)
|
|
21
|
+
return '11-50';
|
|
22
|
+
if (version <= 100)
|
|
23
|
+
return '51-100';
|
|
24
|
+
if (version <= 500)
|
|
25
|
+
return '101-500';
|
|
26
|
+
return '500+';
|
|
27
|
+
}
|
|
28
|
+
export function sanitizeRoute(route) {
|
|
29
|
+
return route
|
|
30
|
+
.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '/:id')
|
|
31
|
+
.replace(/\/\d+/g, '/:id');
|
|
32
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { ExceptionsFilter } from '../error-handling/all-exceptions.filter';
|
|
8
|
+
import { Module } from '@nestjs/common';
|
|
9
|
+
import { makeCounterProvider, makeGaugeProvider, makeHistogramProvider, } from '@willsoto/nestjs-prometheus';
|
|
10
|
+
import { METRICS_PORT } from './metrics.port';
|
|
11
|
+
import { PrometheusService } from './prometheus.service';
|
|
12
|
+
let MetricsModule = class MetricsModule {
|
|
13
|
+
};
|
|
14
|
+
MetricsModule = __decorate([
|
|
15
|
+
Module({
|
|
16
|
+
providers: [
|
|
17
|
+
{
|
|
18
|
+
provide: METRICS_PORT,
|
|
19
|
+
useClass: PrometheusService,
|
|
20
|
+
},
|
|
21
|
+
ExceptionsFilter,
|
|
22
|
+
makeHistogramProvider({
|
|
23
|
+
name: 'rgs_http_server_requests_duration_ms',
|
|
24
|
+
help: 'Duration of HTTP requests in milliseconds',
|
|
25
|
+
labelNames: ['method', 'route', 'status_code', 'operator_code'],
|
|
26
|
+
buckets: [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
|
|
27
|
+
}),
|
|
28
|
+
makeGaugeProvider({
|
|
29
|
+
name: 'rgs_ws_connections',
|
|
30
|
+
help: 'Current number of WebSocket connections',
|
|
31
|
+
labelNames: ['state', 'operator_code'],
|
|
32
|
+
}),
|
|
33
|
+
makeCounterProvider({
|
|
34
|
+
name: 'rgs_ws_messages_total',
|
|
35
|
+
help: 'Total WebSocket messages sent or received',
|
|
36
|
+
labelNames: ['type', 'operator_code', 'event_name'],
|
|
37
|
+
}),
|
|
38
|
+
makeHistogramProvider({
|
|
39
|
+
name: 'rgs_ws_event_duration_ms',
|
|
40
|
+
help: 'Duration of WebSocket event processing in milliseconds',
|
|
41
|
+
labelNames: ['event_name', 'operator_code'],
|
|
42
|
+
buckets: [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
|
|
43
|
+
}),
|
|
44
|
+
makeCounterProvider({
|
|
45
|
+
name: 'rgs_sessions_total',
|
|
46
|
+
help: 'Total number of game sessions by final status',
|
|
47
|
+
labelNames: ['operator_code', 'game_code', 'jurisdiction', 'status'],
|
|
48
|
+
}),
|
|
49
|
+
makeGaugeProvider({
|
|
50
|
+
name: 'rgs_sessions_active',
|
|
51
|
+
help: 'Current number of active game sessions',
|
|
52
|
+
labelNames: ['operator_code', 'game_code', 'jurisdiction'],
|
|
53
|
+
}),
|
|
54
|
+
makeHistogramProvider({
|
|
55
|
+
name: 'rgs_session_duration_ms',
|
|
56
|
+
help: 'Game session duration from init to end in milliseconds',
|
|
57
|
+
labelNames: ['operator_code', 'game_code', 'jurisdiction', 'end_status'],
|
|
58
|
+
buckets: [10000, 30000, 60000, 120000, 300000, 600000, 1800000, 3600000],
|
|
59
|
+
}),
|
|
60
|
+
makeCounterProvider({
|
|
61
|
+
name: 'rgs_rounds_completed_total',
|
|
62
|
+
help: 'Total game rounds completed',
|
|
63
|
+
labelNames: ['operator_code', 'game_code', 'jurisdiction', 'result'],
|
|
64
|
+
}),
|
|
65
|
+
makeHistogramProvider({
|
|
66
|
+
name: 'rgs_wager_minor_units',
|
|
67
|
+
help: 'Wager amounts in minor units',
|
|
68
|
+
labelNames: ['operator_code', 'game_code', 'jurisdiction', 'currency'],
|
|
69
|
+
buckets: [10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000],
|
|
70
|
+
}),
|
|
71
|
+
makeHistogramProvider({
|
|
72
|
+
name: 'rgs_win_minor_units',
|
|
73
|
+
help: 'Win amounts in minor units',
|
|
74
|
+
labelNames: ['operator_code', 'game_code', 'jurisdiction', 'currency'],
|
|
75
|
+
buckets: [10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000],
|
|
76
|
+
}),
|
|
77
|
+
makeCounterProvider({
|
|
78
|
+
name: 'rgs_wallet_requests_total',
|
|
79
|
+
help: 'Total wallet API requests',
|
|
80
|
+
labelNames: ['operation_type', 'operator_code', 'currency', 'status'],
|
|
81
|
+
}),
|
|
82
|
+
makeHistogramProvider({
|
|
83
|
+
name: 'rgs_wallet_requests_duration_ms',
|
|
84
|
+
help: 'Duration of wallet API requests in milliseconds',
|
|
85
|
+
labelNames: ['operation_type', 'operator_code', 'status'],
|
|
86
|
+
buckets: [10, 50, 100, 250, 500, 1000, 2000, 5000, 10000],
|
|
87
|
+
}),
|
|
88
|
+
makeHistogramProvider({
|
|
89
|
+
name: 'game_engine_command_duration_ms',
|
|
90
|
+
help: 'Duration of game engine command processing in milliseconds',
|
|
91
|
+
labelNames: ['command_type', 'game_code', 'game_version'],
|
|
92
|
+
buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1000],
|
|
93
|
+
}),
|
|
94
|
+
makeCounterProvider({
|
|
95
|
+
name: 'game_engine_commands_total',
|
|
96
|
+
help: 'Total game engine commands processed',
|
|
97
|
+
labelNames: ['command_type', 'game_code', 'game_version'],
|
|
98
|
+
}),
|
|
99
|
+
makeCounterProvider({
|
|
100
|
+
name: 'game_engine_errors_total',
|
|
101
|
+
help: 'Total game engine errors or exceptions',
|
|
102
|
+
labelNames: ['game_code', 'game_version', 'error_type'],
|
|
103
|
+
}),
|
|
104
|
+
makeCounterProvider({
|
|
105
|
+
name: 'game_engine_rng_calls_total',
|
|
106
|
+
help: 'Total RNG calls made by game engine',
|
|
107
|
+
labelNames: ['game_code', 'game_version'],
|
|
108
|
+
}),
|
|
109
|
+
makeHistogramProvider({
|
|
110
|
+
name: 'rgs_event_store_write_duration_ms',
|
|
111
|
+
help: 'Event store write duration',
|
|
112
|
+
labelNames: ['aggregate_type', 'event_count_bucket'],
|
|
113
|
+
buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2000],
|
|
114
|
+
}),
|
|
115
|
+
makeHistogramProvider({
|
|
116
|
+
name: 'rgs_event_store_read_duration_ms',
|
|
117
|
+
help: 'Event store read duration',
|
|
118
|
+
labelNames: ['aggregate_type', 'stream_version_bucket'],
|
|
119
|
+
buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2000],
|
|
120
|
+
}),
|
|
121
|
+
makeHistogramProvider({
|
|
122
|
+
name: 'rgs_aggregate_reconstruction_duration_ms',
|
|
123
|
+
help: 'Aggregate reconstruction time',
|
|
124
|
+
labelNames: ['aggregate_type', 'events_count_bucket'],
|
|
125
|
+
buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2000],
|
|
126
|
+
}),
|
|
127
|
+
makeHistogramProvider({
|
|
128
|
+
name: 'rgs_aggregate_reconstruction_events',
|
|
129
|
+
help: 'Number of events in reconstruction',
|
|
130
|
+
labelNames: ['aggregate_type'],
|
|
131
|
+
buckets: [1, 5, 10, 25, 50, 100, 250, 500],
|
|
132
|
+
}),
|
|
133
|
+
makeGaugeProvider({
|
|
134
|
+
name: 'rgs_db_connection_pool_size',
|
|
135
|
+
help: 'Connection pool size',
|
|
136
|
+
labelNames: ['pool_type', 'state'],
|
|
137
|
+
}),
|
|
138
|
+
makeCounterProvider({
|
|
139
|
+
name: 'rgs_db_connection_errors_total',
|
|
140
|
+
help: 'Database connection errors',
|
|
141
|
+
labelNames: ['error_type'],
|
|
142
|
+
}),
|
|
143
|
+
makeGaugeProvider({
|
|
144
|
+
name: 'rgs_games_total',
|
|
145
|
+
help: 'Total games in catalog',
|
|
146
|
+
labelNames: ['status'],
|
|
147
|
+
}),
|
|
148
|
+
makeGaugeProvider({
|
|
149
|
+
name: 'rgs_game_configs_total',
|
|
150
|
+
help: 'Total game configurations per jurisdiction',
|
|
151
|
+
labelNames: ['jurisdiction'],
|
|
152
|
+
}),
|
|
153
|
+
makeGaugeProvider({
|
|
154
|
+
name: 'rgs_enabled_games_per_operator',
|
|
155
|
+
help: 'Enabled games count per operator',
|
|
156
|
+
labelNames: ['operator_code', 'jurisdiction'],
|
|
157
|
+
}),
|
|
158
|
+
makeGaugeProvider({
|
|
159
|
+
name: 'rng_buffer_capacity',
|
|
160
|
+
help: 'Capacity of the RNG buffer',
|
|
161
|
+
labelNames: ['rng_range', 'engine_type', 'game_code'],
|
|
162
|
+
}),
|
|
163
|
+
makeGaugeProvider({
|
|
164
|
+
name: 'rng_buffer_fill_level',
|
|
165
|
+
help: 'Current fill level of the RNG buffer',
|
|
166
|
+
labelNames: ['rng_range', 'engine_type', 'game_code'],
|
|
167
|
+
}),
|
|
168
|
+
makeGaugeProvider({
|
|
169
|
+
name: 'rng_buffer_fill_ratio',
|
|
170
|
+
help: 'Current fill ratio of the RNG buffer',
|
|
171
|
+
labelNames: ['rng_range', 'engine_type', 'game_code'],
|
|
172
|
+
}),
|
|
173
|
+
makeCounterProvider({
|
|
174
|
+
name: 'rng_buffer_refill_requests_total',
|
|
175
|
+
help: 'Total number of RNG buffer refill requests',
|
|
176
|
+
labelNames: ['rng_range', 'engine_type', 'reason', 'game_code'],
|
|
177
|
+
}),
|
|
178
|
+
makeCounterProvider({
|
|
179
|
+
name: 'rng_buffer_refill_failures_total',
|
|
180
|
+
help: 'Total number of failed RNG buffer refill requests',
|
|
181
|
+
labelNames: ['rng_range', 'engine_type', 'reason', 'error', 'game_code'],
|
|
182
|
+
}),
|
|
183
|
+
makeHistogramProvider({
|
|
184
|
+
name: 'rng_buffer_refill_duration_ms',
|
|
185
|
+
help: 'Duration of RNG buffer refill operations in milliseconds',
|
|
186
|
+
labelNames: ['rng_range', 'engine_type', 'game_code'],
|
|
187
|
+
buckets: [1, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
|
|
188
|
+
}),
|
|
189
|
+
makeCounterProvider({
|
|
190
|
+
name: 'rng_buffer_starvation_events_total',
|
|
191
|
+
help: 'Total number of RNG buffer starvation events',
|
|
192
|
+
labelNames: ['rng_range', 'engine_type', 'game_code'],
|
|
193
|
+
}),
|
|
194
|
+
makeHistogramProvider({
|
|
195
|
+
name: 'rng_buffer_latency_ms',
|
|
196
|
+
help: 'Latency of RNG buffer operations in milliseconds',
|
|
197
|
+
labelNames: ['rng_range', 'engine_type', 'game_code'],
|
|
198
|
+
buckets: [1, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
|
|
199
|
+
}),
|
|
200
|
+
],
|
|
201
|
+
exports: [METRICS_PORT],
|
|
202
|
+
})
|
|
203
|
+
], MetricsModule);
|
|
204
|
+
export { MetricsModule };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const METRICS_PORT = Symbol('METRICS_PORT');
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { Injectable } from '@nestjs/common';
|
|
14
|
+
import { InjectMetric } from '@willsoto/nestjs-prometheus';
|
|
15
|
+
import { Counter as PromCounter, Gauge as PromGauge, Histogram as PromHistogram, } from 'prom-client';
|
|
16
|
+
let PrometheusService = class PrometheusService {
|
|
17
|
+
constructor(engineCommandsTotal, engineErrorsTotal, engineRngCallsTotal, engineCommandDuration, rngBufferCapacity, rngBufferFillLevel, rngBufferFillRatio, rngBufferRefillRequestsTotal, rngBufferRefillFailuresTotal, rngBufferRefillDuration, rngBufferStarvationEventsTotal, rngBufferLatency) {
|
|
18
|
+
this.engineCommandsTotal = engineCommandsTotal;
|
|
19
|
+
this.engineErrorsTotal = engineErrorsTotal;
|
|
20
|
+
this.engineRngCallsTotal = engineRngCallsTotal;
|
|
21
|
+
this.engineCommandDuration = engineCommandDuration;
|
|
22
|
+
this.rngBufferCapacity = rngBufferCapacity;
|
|
23
|
+
this.rngBufferFillLevel = rngBufferFillLevel;
|
|
24
|
+
this.rngBufferFillRatio = rngBufferFillRatio;
|
|
25
|
+
this.rngBufferRefillRequestsTotal = rngBufferRefillRequestsTotal;
|
|
26
|
+
this.rngBufferRefillFailuresTotal = rngBufferRefillFailuresTotal;
|
|
27
|
+
this.rngBufferRefillDuration = rngBufferRefillDuration;
|
|
28
|
+
this.rngBufferStarvationEventsTotal = rngBufferStarvationEventsTotal;
|
|
29
|
+
this.rngBufferLatency = rngBufferLatency;
|
|
30
|
+
this.metricsRegistry = new Map();
|
|
31
|
+
this.initializeRegistry();
|
|
32
|
+
}
|
|
33
|
+
initializeMetricsFromDatabase() {
|
|
34
|
+
throw new Error('Method not implemented.');
|
|
35
|
+
}
|
|
36
|
+
initializeRegistry() {
|
|
37
|
+
this.metricsRegistry.set('game_engine_commands_total', this.engineCommandsTotal);
|
|
38
|
+
this.metricsRegistry.set('game_engine_errors_total', this.engineErrorsTotal);
|
|
39
|
+
this.metricsRegistry.set('game_engine_rng_calls_total', this.engineRngCallsTotal);
|
|
40
|
+
this.metricsRegistry.set('game_engine_command_duration_ms', this.engineCommandDuration);
|
|
41
|
+
this.metricsRegistry.set('rng_buffer_capacity', this.rngBufferCapacity);
|
|
42
|
+
this.metricsRegistry.set('rng_buffer_fill_level', this.rngBufferFillLevel);
|
|
43
|
+
this.metricsRegistry.set('rng_buffer_fill_ratio', this.rngBufferFillRatio);
|
|
44
|
+
this.metricsRegistry.set('rng_buffer_refill_requests_total', this.rngBufferRefillRequestsTotal);
|
|
45
|
+
this.metricsRegistry.set('rng_buffer_refill_failures_total', this.rngBufferRefillFailuresTotal);
|
|
46
|
+
this.metricsRegistry.set('rng_buffer_refill_duration_ms', this.rngBufferRefillDuration);
|
|
47
|
+
this.metricsRegistry.set('rng_buffer_starvation_events_total', this.rngBufferStarvationEventsTotal);
|
|
48
|
+
this.metricsRegistry.set('rng_buffer_latency_ms', this.rngBufferLatency);
|
|
49
|
+
}
|
|
50
|
+
getCounter(name) {
|
|
51
|
+
const metric = this.metricsRegistry.get(name);
|
|
52
|
+
if (!metric) {
|
|
53
|
+
throw new Error(`Counter ${name} not found in metrics registry`);
|
|
54
|
+
}
|
|
55
|
+
return metric;
|
|
56
|
+
}
|
|
57
|
+
getGauge(name) {
|
|
58
|
+
const metric = this.metricsRegistry.get(name);
|
|
59
|
+
if (!metric) {
|
|
60
|
+
throw new Error(`Gauge ${name} not found in metrics registry`);
|
|
61
|
+
}
|
|
62
|
+
return metric;
|
|
63
|
+
}
|
|
64
|
+
getHistogram(name) {
|
|
65
|
+
const metric = this.metricsRegistry.get(name);
|
|
66
|
+
if (!metric) {
|
|
67
|
+
throw new Error(`Histogram ${name} not found in metrics registry`);
|
|
68
|
+
}
|
|
69
|
+
return metric;
|
|
70
|
+
}
|
|
71
|
+
async getMetrics() {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
PrometheusService = __decorate([
|
|
76
|
+
Injectable(),
|
|
77
|
+
__param(0, InjectMetric('game_engine_commands_total')),
|
|
78
|
+
__param(1, InjectMetric('game_engine_errors_total')),
|
|
79
|
+
__param(2, InjectMetric('game_engine_rng_calls_total')),
|
|
80
|
+
__param(3, InjectMetric('game_engine_command_duration_ms')),
|
|
81
|
+
__param(4, InjectMetric('rng_buffer_capacity')),
|
|
82
|
+
__param(5, InjectMetric('rng_buffer_fill_level')),
|
|
83
|
+
__param(6, InjectMetric('rng_buffer_fill_ratio')),
|
|
84
|
+
__param(7, InjectMetric('rng_buffer_refill_requests_total')),
|
|
85
|
+
__param(8, InjectMetric('rng_buffer_refill_failures_total')),
|
|
86
|
+
__param(9, InjectMetric('rng_buffer_refill_duration_ms')),
|
|
87
|
+
__param(10, InjectMetric('rng_buffer_starvation_events_total')),
|
|
88
|
+
__param(11, InjectMetric('rng_buffer_latency_ms')),
|
|
89
|
+
__metadata("design:paramtypes", [PromCounter,
|
|
90
|
+
PromCounter,
|
|
91
|
+
PromCounter,
|
|
92
|
+
PromHistogram,
|
|
93
|
+
PromGauge,
|
|
94
|
+
PromGauge,
|
|
95
|
+
PromGauge,
|
|
96
|
+
PromCounter,
|
|
97
|
+
PromCounter,
|
|
98
|
+
PromHistogram,
|
|
99
|
+
PromCounter,
|
|
100
|
+
PromHistogram])
|
|
101
|
+
], PrometheusService);
|
|
102
|
+
export { PrometheusService };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RetryPolicy } from './retry-policy';
|
|
2
|
+
export const HTTP_RETRYABLE_ERRORS = [
|
|
3
|
+
'TIMEOUT',
|
|
4
|
+
'ETIMEDOUT',
|
|
5
|
+
'ECONNRESET',
|
|
6
|
+
'ECONNREFUSED',
|
|
7
|
+
'EAI_AGAIN',
|
|
8
|
+
'ENOTFOUND',
|
|
9
|
+
'NETWORK',
|
|
10
|
+
];
|
|
11
|
+
export class RetryPolicies {
|
|
12
|
+
static walletOperation() {
|
|
13
|
+
return new RetryPolicy({
|
|
14
|
+
maxAttempts: 5,
|
|
15
|
+
delayMs: 200,
|
|
16
|
+
backoffMultiplier: 2,
|
|
17
|
+
maxDelayMs: 3000,
|
|
18
|
+
retryableErrors: HTTP_RETRYABLE_ERRORS,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
static externalApi() {
|
|
22
|
+
return new RetryPolicy({
|
|
23
|
+
maxAttempts: 3,
|
|
24
|
+
delayMs: 100,
|
|
25
|
+
backoffMultiplier: 2,
|
|
26
|
+
maxDelayMs: 1000,
|
|
27
|
+
retryableErrors: HTTP_RETRYABLE_ERRORS,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
static database() {
|
|
31
|
+
return new RetryPolicy({
|
|
32
|
+
maxAttempts: 3,
|
|
33
|
+
delayMs: 50,
|
|
34
|
+
backoffMultiplier: 2,
|
|
35
|
+
maxDelayMs: 500,
|
|
36
|
+
retryableErrors: ['deadlock', 'serialization', 'timeout'],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Logger } from '../logger/logger';
|
|
2
|
+
export class RetryPolicy {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.logger = new Logger(RetryPolicy.name);
|
|
5
|
+
this.maxAttempts = Math.max(1, options.maxAttempts);
|
|
6
|
+
this.delayMs = Math.max(0, options.delayMs);
|
|
7
|
+
this.backoffMultiplier = options.backoffMultiplier ?? 1;
|
|
8
|
+
this.maxDelayMs = options.maxDelayMs ?? Number.POSITIVE_INFINITY;
|
|
9
|
+
this.retryableErrors = options.retryableErrors;
|
|
10
|
+
this.onRetry = options.onRetry;
|
|
11
|
+
this.operationName = options.operationName;
|
|
12
|
+
}
|
|
13
|
+
async execute(fn) {
|
|
14
|
+
let lastError = null;
|
|
15
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
|
|
16
|
+
try {
|
|
17
|
+
const result = await fn();
|
|
18
|
+
if (attempt > 1) {
|
|
19
|
+
this.logger.log(`${this.operationName ?? 'operation'} succeeded on attempt ${attempt}`);
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const err = error;
|
|
25
|
+
lastError = err;
|
|
26
|
+
if (!this.isRetryable(err)) {
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
if (attempt === this.maxAttempts) {
|
|
30
|
+
this.logger.error(`${this.operationName ?? 'operation'} failed after ${attempt} attempts: ${err.message}`, err);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
const delay = this.calculateDelay(attempt);
|
|
34
|
+
this.logger.warn(`${this.operationName ?? 'operation'} failed on attempt ${attempt}/${this.maxAttempts} with error: ${err.message}. Retrying in ${delay}ms...`);
|
|
35
|
+
if (this.onRetry) {
|
|
36
|
+
try {
|
|
37
|
+
this.onRetry(attempt, err);
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
}
|
|
41
|
+
await this.sleep(delay);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
throw lastError ?? new Error('RetryPolicy failed without error');
|
|
45
|
+
}
|
|
46
|
+
isRetryable(error) {
|
|
47
|
+
if (!this.retryableErrors || this.retryableErrors.length === 0)
|
|
48
|
+
return true;
|
|
49
|
+
const causeCode = error?.cause?.code ?? '';
|
|
50
|
+
const text = `${error?.code ?? ''} ${error?.name ?? ''} ${error.message} ${causeCode}`;
|
|
51
|
+
return this.retryableErrors.some(pattern => text.includes(pattern));
|
|
52
|
+
}
|
|
53
|
+
calculateDelay(attempt) {
|
|
54
|
+
const factor = Math.max(1, this.backoffMultiplier);
|
|
55
|
+
const raw = this.delayMs * Math.pow(factor, attempt - 1);
|
|
56
|
+
return Math.min(raw, this.maxDelayMs);
|
|
57
|
+
}
|
|
58
|
+
async sleep(ms) {
|
|
59
|
+
await new Promise(resolve => setTimeout(resolve, ms));
|
|
60
|
+
}
|
|
61
|
+
}
|