@omnitronix/bonnys-fortune-game-engine 1.3.1 → 1.3.2
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/README.md +15 -15
- package/dist/__tests__/bonnys-fortune-v1.game-engine.test.d.ts +1 -0
- package/dist/bonnys-fortune-v1.game-engine.d.ts +36 -0
- package/dist/bonnys-fortune-v1.game-engine.js +23 -12
- package/dist/bonnys-fortune-v1.game-engine.js.map +1 -1
- package/dist/config/game-logic-config/file-system.game-logic-config-loader.d.ts +4 -0
- package/dist/config/game-logic-config/game-logic-config-loader.d.ts +3 -0
- package/dist/config/game-logic-config/game-logic-config.d.ts +2 -0
- package/dist/config/game-logic-config/game-logic-config.js +2 -0
- package/dist/config/game-logic-config/game-logic-config.js.map +1 -1
- package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.d.ts +4 -0
- package/dist/config/reel-strips-config/reel-strip-option.dto.d.ts +4 -0
- package/dist/config/reel-strips-config/reel-strips-config-loader.d.ts +3 -0
- package/dist/config/reel-strips-config/reel-strips-config.dto.d.ts +5 -0
- package/dist/config/reel-strips-config/reel.dto.d.ts +5 -0
- package/dist/domain/game-round.types.d.ts +9 -0
- package/dist/domain/mappers/reel-strips-config.mapper.d.ts +4 -0
- package/dist/domain/reel-strip-option.d.ts +7 -0
- package/dist/domain/reel-strips-config.d.ts +9 -0
- package/dist/domain/reel.d.ts +8 -0
- package/dist/domain/types/cheat-trigger-bonus-command.d.ts +5 -0
- package/dist/domain/types/{debug-trigger-bonus-command.js → cheat-trigger-bonus-command.js} +1 -1
- package/dist/domain/types/cheat-trigger-bonus-command.js.map +1 -0
- package/dist/domain/types/cheat-trigger-bonus-request.dto.d.ts +5 -0
- package/dist/domain/types/{debug-trigger-bonus-request.dto.js → cheat-trigger-bonus-request.dto.js} +1 -1
- package/dist/domain/types/cheat-trigger-bonus-request.dto.js.map +1 -0
- package/dist/domain/types/cheat-update-bonus-meter-progress-command.d.ts +5 -0
- package/dist/domain/types/{debug-update-bonus-meter-progress-command.js → cheat-update-bonus-meter-progress-command.js} +1 -1
- package/dist/domain/types/cheat-update-bonus-meter-progress-command.js.map +1 -0
- package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.d.ts +5 -0
- package/dist/domain/types/{debug-update-bonus-meter-progress-request.dto.js → cheat-update-bonus-meter-progress-request.dto.js} +1 -1
- package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.js.map +1 -0
- package/dist/domain/types/game-spin-command.d.ts +8 -0
- package/dist/domain/types/game-spin-input.dto.d.ts +8 -0
- package/dist/domain/types/game-spin-output.dto.d.ts +119 -0
- package/dist/domain/types/game-symbols.response.dto.d.ts +10 -0
- package/dist/domain/types/reel-strip-option.dto.d.ts +4 -0
- package/dist/domain/types/reel-strips-config.dto.d.ts +5 -0
- package/dist/domain/types/reel.dto.d.ts +5 -0
- package/dist/domain/types/start-bonus-round-command.d.ts +8 -0
- package/dist/domain/types/start-bonus-round-input.dto.d.ts +10 -0
- package/dist/game-engine.interface.d.ts +41 -0
- package/dist/helpers/generate-hash.d.ts +1 -0
- package/dist/helpers/number-helper.d.ts +23 -0
- package/dist/helpers/optional-boolean-mapper.d.ts +1 -0
- package/dist/helpers/uuid-helper.d.ts +3 -0
- package/dist/helpers/validation-helper.d.ts +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js.map +1 -1
- package/dist/logger/logger.d.ts +22 -0
- package/dist/logger/logger.js +4 -5
- package/dist/logger/logger.js.map +1 -1
- package/dist/logic/bonnys-fortune.game-logic.d.ts +32 -0
- package/dist/logic/bonnys-fortune.game-logic.js +15 -11
- package/dist/logic/bonnys-fortune.game-logic.js.map +1 -1
- package/dist/logic/bonnys-fortune.spin-generator.d.ts +19 -0
- package/dist/logic/bonnys-fortune.types.d.ts +170 -0
- package/dist/logic/bonnys-fortune.win-calculator.d.ts +10 -0
- package/dist/logic/game-logic-config.interface.d.ts +180 -0
- package/dist/logic/handlers/base-game.handler.d.ts +15 -0
- package/dist/logic/handlers/base-game.handler.js +5 -1
- package/dist/logic/handlers/base-game.handler.js.map +1 -1
- package/dist/logic/handlers/collect-feature-bonus.handler.d.ts +13 -0
- package/dist/logic/handlers/free-spins-bonus.handler.d.ts +11 -0
- package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.d.ts +11 -0
- package/dist/logic/handlers/treasure-hunt-bonus.handler.d.ts +15 -0
- package/dist/rng/DummyRngClient.d.ts +18 -0
- package/dist/rng/DummyRngClient.js +1 -8
- package/dist/rng/DummyRngClient.js.map +1 -1
- package/dist/rng/rng-client.factory.d.ts +7 -0
- package/dist/rng/rng-client.factory.js.map +1 -1
- package/dist/rng/rng-client.interface.d.ts +16 -0
- package/dist/rng/rng-service.d.ts +26 -0
- package/dist/validation/abstract-game-logic-config.validator.d.ts +4 -0
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.d.ts +131 -0
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js +9 -0
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js.map +1 -1
- package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.d.ts +5 -0
- package/dist/validation/custom-decorators/IsNestedIntArray.d.ts +2 -0
- package/dist/validation/game-logic-config-validation.service.d.ts +8 -0
- package/dist/validation/reel-strips-config-validation.service.d.ts +5 -0
- package/package.json +4 -3
- package/src/__tests__/bonnys-fortune-v1.game-engine.test.ts +80 -0
- package/src/bonnys-fortune-v1.game-engine.ts +314 -0
- package/src/config/game-logic-config/file-system.game-logic-config-loader.ts +27 -0
- package/src/config/game-logic-config/game-logic-config-loader.ts +3 -0
- package/src/config/game-logic-config/game-logic-config.ts +382 -0
- package/src/config/reel-strips-config/file-system.reel-strips-config-loader.ts +43 -0
- package/src/config/reel-strips-config/reel-strip-option.dto.ts +13 -0
- package/src/config/reel-strips-config/reel-strips-config-loader.ts +9 -0
- package/src/config/reel-strips-config/reel-strips-config.dto.ts +16 -0
- package/src/config/reel-strips-config/reel.dto.ts +16 -0
- package/src/config/reel-strips-config/reels-BASE.csv +52 -0
- package/src/config/reel-strips-config/reels-BONUS.csv +52 -0
- package/src/domain/game-round.types.ts +10 -0
- package/src/domain/mappers/reel-strips-config.mapper.ts +16 -0
- package/src/domain/reel-strip-option.ts +15 -0
- package/src/domain/reel-strips-config.ts +21 -0
- package/src/domain/reel.ts +17 -0
- package/src/domain/types/cheat-trigger-bonus-command.ts +5 -0
- package/src/domain/types/cheat-trigger-bonus-request.dto.ts +5 -0
- package/src/domain/types/cheat-update-bonus-meter-progress-command.ts +6 -0
- package/src/domain/types/cheat-update-bonus-meter-progress-request.dto.ts +5 -0
- package/src/domain/types/game-spin-command.ts +8 -0
- package/src/domain/types/game-spin-input.dto.ts +8 -0
- package/src/domain/types/game-spin-output.dto.ts +142 -0
- package/src/domain/types/game-symbols.response.dto.ts +11 -0
- package/src/domain/types/reel-strip-option.dto.ts +13 -0
- package/src/domain/types/reel-strips-config.dto.ts +15 -0
- package/src/domain/types/reel.dto.ts +15 -0
- package/src/domain/types/start-bonus-round-command.ts +8 -0
- package/src/domain/types/start-bonus-round-input.dto.ts +10 -0
- package/src/game-engine.interface.ts +59 -0
- package/src/helpers/generate-hash.ts +5 -0
- package/src/helpers/number-helper.ts +41 -0
- package/src/helpers/optional-boolean-mapper.ts +5 -0
- package/src/helpers/uuid-helper.ts +7 -0
- package/src/helpers/validation-helper.ts +27 -0
- package/src/index.ts +3 -0
- package/src/logger/logger.ts +178 -0
- package/src/logic/bonnys-fortune.game-logic.ts +490 -0
- package/src/logic/bonnys-fortune.spin-generator.ts +277 -0
- package/src/logic/bonnys-fortune.types.ts +223 -0
- package/src/logic/bonnys-fortune.win-calculator.ts +210 -0
- package/src/logic/game-logic-config.interface.ts +176 -0
- package/src/logic/handlers/base-game.handler.ts +221 -0
- package/src/logic/handlers/collect-feature-bonus.handler.ts +301 -0
- package/src/logic/handlers/free-spins-bonus.handler.ts +119 -0
- package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +118 -0
- package/src/logic/handlers/treasure-hunt-bonus.handler.ts +232 -0
- package/src/rng/DummyRngClient.ts +108 -0
- package/src/rng/rng-client.factory.ts +27 -0
- package/src/rng/rng-client.interface.ts +38 -0
- package/src/rng/rng-service.ts +130 -0
- package/src/validation/abstract-game-logic-config.validator.ts +20 -0
- package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +379 -0
- package/src/validation/bonnys-fortune/bonnys-fortune-config.validator.ts +8 -0
- package/src/validation/custom-decorators/IsNestedIntArray.ts +29 -0
- package/src/validation/game-logic-config-validation.service.ts +28 -0
- package/src/validation/reel-strips-config-validation.service.ts +29 -0
- package/dist/__tests__/comprehensive.test.js +0 -741
- package/dist/__tests__/comprehensive.test.js.map +0 -1
- package/dist/domain/types/debug-trigger-bonus-command.js.map +0 -1
- package/dist/domain/types/debug-trigger-bonus-request.dto.js.map +0 -1
- package/dist/domain/types/debug-update-bonus-meter-progress-command.js.map +0 -1
- package/dist/domain/types/debug-update-bonus-meter-progress-request.dto.js.map +0 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
export enum SpinType {
|
|
2
|
+
BASE_GAME_SPIN = 'BASE_GAME_SPIN',
|
|
3
|
+
BONUS_GAME_1_SPIN = 'BONUS_GAME_1_SPIN',
|
|
4
|
+
BONUS_GAME_2_SPIN = 'BONUS_GAME_2_SPIN',
|
|
5
|
+
BONUS_GAME_3_SPIN = 'BONUS_GAME_3_SPIN',
|
|
6
|
+
COLLECT_FEATURE_SPIN = 'COLLECT_FEATURE_SPIN',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export enum PrizeType {
|
|
10
|
+
MULTIPLIER = 'MULTIPLIER',
|
|
11
|
+
BONUS = 'BONUS',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Triggered bonus */
|
|
15
|
+
export interface TriggeredBonusDto {
|
|
16
|
+
bonusId: string;
|
|
17
|
+
bonusType: string;
|
|
18
|
+
betAmount: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Winning line */
|
|
22
|
+
export interface WinningLineDto {
|
|
23
|
+
win: number;
|
|
24
|
+
ways: number;
|
|
25
|
+
symbol: number;
|
|
26
|
+
positions: number[][];
|
|
27
|
+
multiplier: number;
|
|
28
|
+
consecutiveReels: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Win result */
|
|
32
|
+
export interface WinResultDto {
|
|
33
|
+
totalWin: number;
|
|
34
|
+
winningLines: WinningLineDto[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Bonus meter */
|
|
38
|
+
export interface BonusMeterDto {
|
|
39
|
+
progress: number;
|
|
40
|
+
symbolId: number;
|
|
41
|
+
threshold: number;
|
|
42
|
+
symbolName: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Public state */
|
|
46
|
+
export interface PublicStateDto {
|
|
47
|
+
bonusMeters: Record<string, BonusMeterDto>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Base Game Spin */
|
|
51
|
+
export interface BaseGameSpinResultDto {
|
|
52
|
+
spinType: SpinType;
|
|
53
|
+
screen: number[][];
|
|
54
|
+
winResult: WinResultDto;
|
|
55
|
+
playerWinning: number;
|
|
56
|
+
nextSpinType: SpinType;
|
|
57
|
+
triggeredBonus?: TriggeredBonusDto;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Treasure Hunt (Bonus Game 1) */
|
|
61
|
+
export interface TreasureHuntSpinResultDto {
|
|
62
|
+
spinType: SpinType;
|
|
63
|
+
selection: string;
|
|
64
|
+
multiplier: number;
|
|
65
|
+
multiplierCounts: Record<string, number>;
|
|
66
|
+
winningMultiplier: number | null;
|
|
67
|
+
winningCount: number;
|
|
68
|
+
totalMultiplier: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Free Spin (Bonus Game 2) */
|
|
72
|
+
export interface FreeSpinResultDto {
|
|
73
|
+
spinType: SpinType;
|
|
74
|
+
screen: number[][];
|
|
75
|
+
playerWinning: number;
|
|
76
|
+
nextSpinType: SpinType;
|
|
77
|
+
multiplier: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Steering to the Fortune (Bonus Game 3) */
|
|
81
|
+
export interface SteeringToTheFortuneResultDto {
|
|
82
|
+
spinType: SpinType;
|
|
83
|
+
playerWinning: number;
|
|
84
|
+
nextSpinType: SpinType;
|
|
85
|
+
prizeType: PrizeType;
|
|
86
|
+
prizeValue?: number | string;
|
|
87
|
+
totalMultiplier: number;
|
|
88
|
+
triggeredBonus?: TriggeredBonusDto;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Collect Feature Spin */
|
|
92
|
+
export interface CollectFeatureSpinResultDto {
|
|
93
|
+
spinIndex?: number;
|
|
94
|
+
spinType: SpinType;
|
|
95
|
+
symbols?: string[];
|
|
96
|
+
slashed?: boolean[];
|
|
97
|
+
currentValues?: number;
|
|
98
|
+
currentMultipliers?: number;
|
|
99
|
+
totalValues?: number;
|
|
100
|
+
totalMultipliers?: number;
|
|
101
|
+
playerWinning: number;
|
|
102
|
+
totalWin?: number;
|
|
103
|
+
spinsLeft?: number;
|
|
104
|
+
nextSpinType: SpinType;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface BaseGameSpinResultWrapperDto {
|
|
108
|
+
result: BaseGameSpinResultDto;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface FreeSpinResultWrapperDto {
|
|
112
|
+
result: FreeSpinResultDto;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface TreasureHuntSpinResultWrapperDto {
|
|
116
|
+
result: TreasureHuntSpinResultDto;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface SteeringToTheFortuneResultWrapperDto {
|
|
120
|
+
result: SteeringToTheFortuneResultDto;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface CollectFeatureSpinResultWrapperDto {
|
|
124
|
+
result: CollectFeatureSpinResultDto;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Game Spin Output DTO */
|
|
128
|
+
export interface GameSpinOutputDto {
|
|
129
|
+
sessionId: string;
|
|
130
|
+
balance: number;
|
|
131
|
+
betAmount: number;
|
|
132
|
+
balanceBeforeSpin: number;
|
|
133
|
+
outcome: BaseGameSpinResultWrapperDto;
|
|
134
|
+
publicState: PublicStateDto;
|
|
135
|
+
lastSpin:
|
|
136
|
+
| BaseGameSpinResultWrapperDto
|
|
137
|
+
| FreeSpinResultWrapperDto
|
|
138
|
+
| TreasureHuntSpinResultWrapperDto
|
|
139
|
+
| SteeringToTheFortuneResultWrapperDto
|
|
140
|
+
| CollectFeatureSpinResultWrapperDto
|
|
141
|
+
| null;
|
|
142
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ArrayNotEmpty, IsArray, IsInt, Min } from 'class-validator';
|
|
2
|
+
|
|
3
|
+
export class ReelStripOptionDto {
|
|
4
|
+
@IsArray()
|
|
5
|
+
@ArrayNotEmpty()
|
|
6
|
+
@IsInt({ each: true })
|
|
7
|
+
@Min(0, { each: true })
|
|
8
|
+
public symbols: number[];
|
|
9
|
+
|
|
10
|
+
constructor(symbols: number[]) {
|
|
11
|
+
this.symbols = symbols;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ArrayNotEmpty, IsArray } from 'class-validator';
|
|
2
|
+
|
|
3
|
+
import { ReelDto } from './reel.dto';
|
|
4
|
+
import { Type } from 'class-transformer';
|
|
5
|
+
|
|
6
|
+
export class ReelStripsConfigDto {
|
|
7
|
+
@IsArray()
|
|
8
|
+
@ArrayNotEmpty()
|
|
9
|
+
@Type(() => ReelDto)
|
|
10
|
+
public reels: ReelDto[];
|
|
11
|
+
|
|
12
|
+
constructor(reels: ReelDto[]) {
|
|
13
|
+
this.reels = reels;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ArrayNotEmpty, IsArray } from 'class-validator';
|
|
2
|
+
|
|
3
|
+
import { ReelStripOptionDto } from './reel-strip-option.dto';
|
|
4
|
+
import { Type } from 'class-transformer';
|
|
5
|
+
|
|
6
|
+
export class ReelDto {
|
|
7
|
+
@IsArray()
|
|
8
|
+
@ArrayNotEmpty()
|
|
9
|
+
@Type(() => ReelStripOptionDto)
|
|
10
|
+
public options: ReelStripOptionDto[];
|
|
11
|
+
|
|
12
|
+
constructor(options: ReelStripOptionDto[]) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { RngClientConfig } from '@omnitronix/rng-client-core';
|
|
2
|
+
|
|
3
|
+
export type GameEngineMetricsHandlers = RngClientConfig['metrics']['handlers'];
|
|
4
|
+
export interface GameEngineConfig {
|
|
5
|
+
metrics?: {
|
|
6
|
+
handlers: GameEngineMetricsHandlers;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface GameEngineInfo {
|
|
11
|
+
gameCode: string;
|
|
12
|
+
version: string;
|
|
13
|
+
rtp: number;
|
|
14
|
+
gameType: string;
|
|
15
|
+
gameName: string;
|
|
16
|
+
provider: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RngOutcomeRecord {
|
|
20
|
+
result: number;
|
|
21
|
+
seed: number;
|
|
22
|
+
min: number;
|
|
23
|
+
max: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type RngOutcome = { [actionId: string]: RngOutcomeRecord };
|
|
27
|
+
|
|
28
|
+
export interface CommandProcessingResult<
|
|
29
|
+
TPublicState = any,
|
|
30
|
+
TPrivateState = any,
|
|
31
|
+
TOutcome = any,
|
|
32
|
+
> {
|
|
33
|
+
success: boolean;
|
|
34
|
+
publicState: TPublicState;
|
|
35
|
+
privateState: TPrivateState;
|
|
36
|
+
outcome?: TOutcome;
|
|
37
|
+
message?: string;
|
|
38
|
+
rngOutcome?: RngOutcome;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface GameActionCommand<TPayload = any> {
|
|
42
|
+
id: string;
|
|
43
|
+
type: string;
|
|
44
|
+
payload?: TPayload;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface GameEngine<
|
|
48
|
+
TPublicState = any,
|
|
49
|
+
TPrivateState = any,
|
|
50
|
+
TOutcome = any,
|
|
51
|
+
> {
|
|
52
|
+
processCommand(
|
|
53
|
+
publicState: TPublicState,
|
|
54
|
+
privateState: TPrivateState,
|
|
55
|
+
command: GameActionCommand,
|
|
56
|
+
): Promise<CommandProcessingResult<TPublicState, TPrivateState, TOutcome>>;
|
|
57
|
+
|
|
58
|
+
getGameEngineInfo(): GameEngineInfo;
|
|
59
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class NumberHelper {
|
|
2
|
+
/**
|
|
3
|
+
* Safely formats a number to the specified decimal places,
|
|
4
|
+
* minimizing floating-point issues.
|
|
5
|
+
*/
|
|
6
|
+
public static toFixedNumber(value: number, decimals: number): number {
|
|
7
|
+
const factor = Math.pow(10, decimals);
|
|
8
|
+
return Math.round((value + Number.EPSILON) * factor) / factor;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Safely adds two numbers and formats result.
|
|
13
|
+
*/
|
|
14
|
+
public static safeSum(a: number, b: number, decimals: number): number {
|
|
15
|
+
const result = a * Math.pow(10, decimals) + b * Math.pow(10, decimals);
|
|
16
|
+
return result / Math.pow(10, decimals);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Safely multiplies two numbers and formats result.
|
|
21
|
+
*/
|
|
22
|
+
public static safeMultiply(a: number, b: number, decimals: number): number {
|
|
23
|
+
const result = a * Math.pow(10, decimals) * (b * Math.pow(10, decimals));
|
|
24
|
+
return result / Math.pow(10, decimals * 2);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Clamps a number between min and max.
|
|
29
|
+
*/
|
|
30
|
+
public static clamp(value: number, min: number, max: number): number {
|
|
31
|
+
return Math.min(Math.max(value, min), max);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Safely divides two numbers.
|
|
36
|
+
*/
|
|
37
|
+
public static safeDivide(a: number, b: number, decimals: number): number {
|
|
38
|
+
if (b === 0) throw new Error('Division by zero');
|
|
39
|
+
return this.toFixedNumber(a / b, decimals);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { validate, ValidationError } from 'class-validator';
|
|
2
|
+
|
|
3
|
+
export class ValidationHelper {
|
|
4
|
+
public static readonly validationOptions = {
|
|
5
|
+
whitelist: true,
|
|
6
|
+
forbidNonWhitelisted: true,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
public static readonly validationOptionsWithoutForbidden = {
|
|
10
|
+
whitelist: true,
|
|
11
|
+
forbidNonWhitelisted: false,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
public static validate<T extends object>(dto: T): Promise<ValidationError[]> {
|
|
15
|
+
return validate(dto, ValidationHelper.validationOptions);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public static validateWithoutForbidden<T extends object>(
|
|
19
|
+
dto: T,
|
|
20
|
+
): Promise<ValidationError[]> {
|
|
21
|
+
return validate(dto, ValidationHelper.validationOptionsWithoutForbidden);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public static isPlainObject(value: unknown): value is Record<string, any> {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Logger as WinstonLogger,
|
|
3
|
+
createLogger,
|
|
4
|
+
format,
|
|
5
|
+
transports,
|
|
6
|
+
} from 'winston';
|
|
7
|
+
|
|
8
|
+
type LogFormat = 'json' | 'pretty';
|
|
9
|
+
|
|
10
|
+
export class Logger {
|
|
11
|
+
private readonly logger: WinstonLogger;
|
|
12
|
+
private context: string;
|
|
13
|
+
|
|
14
|
+
constructor(context: string, logFormat?: LogFormat) {
|
|
15
|
+
this.context = context;
|
|
16
|
+
const format = logFormat ?? this.detectLogFormat();
|
|
17
|
+
this.logger = this.createWinstonLogger(format);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private detectLogFormat(): LogFormat {
|
|
21
|
+
const envFormat = process.env.LOG_FORMAT?.toLowerCase();
|
|
22
|
+
|
|
23
|
+
switch (envFormat) {
|
|
24
|
+
case 'pretty':
|
|
25
|
+
return 'pretty';
|
|
26
|
+
case 'json':
|
|
27
|
+
return 'json';
|
|
28
|
+
default:
|
|
29
|
+
return 'json';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private createWinstonLogger(logFormat: LogFormat): WinstonLogger {
|
|
34
|
+
const winstonFormat = this.getLogFormat(logFormat);
|
|
35
|
+
|
|
36
|
+
return createLogger({
|
|
37
|
+
level: 'info',
|
|
38
|
+
format: winstonFormat,
|
|
39
|
+
transports: this.getTransports(),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private getLogFormat(logFormat: LogFormat) {
|
|
44
|
+
return format.combine(format.timestamp(), this.selectFormat(logFormat));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private selectFormat(logFormat: LogFormat) {
|
|
48
|
+
switch (logFormat) {
|
|
49
|
+
case 'pretty':
|
|
50
|
+
return this.getPrettyFormat();
|
|
51
|
+
case 'json':
|
|
52
|
+
return this.getJsonFormat();
|
|
53
|
+
default:
|
|
54
|
+
return this.getJsonFormat();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private getJsonFormat() {
|
|
59
|
+
return format.combine(
|
|
60
|
+
format((info) => {
|
|
61
|
+
if (this.context) {
|
|
62
|
+
info.message = `[${this.context}] ${info.message}`;
|
|
63
|
+
}
|
|
64
|
+
return info;
|
|
65
|
+
})(),
|
|
66
|
+
format.json(),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private getPrettyFormat() {
|
|
71
|
+
return format.combine(
|
|
72
|
+
format.colorize(),
|
|
73
|
+
format.printf(({ timestamp, level, message, error, ...meta }) => {
|
|
74
|
+
const contextStr = this.context ? `[${this.context}] ` : '';
|
|
75
|
+
let output = `${timestamp} ${level}: ${contextStr}${message}`;
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
error &&
|
|
79
|
+
typeof error === 'object' &&
|
|
80
|
+
'name' in error &&
|
|
81
|
+
'message' in error
|
|
82
|
+
) {
|
|
83
|
+
output += `\n Error: ${error.name}: ${error.message}`;
|
|
84
|
+
|
|
85
|
+
if (error.name === 'DomainException' && 'errorCode' in error) {
|
|
86
|
+
output += `\n Internal Code: ${error.errorCode}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if ('stack' in error && error.stack) {
|
|
90
|
+
output += `\n Stack: ${error.stack}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const metaKeys = Object.keys(meta).filter(
|
|
95
|
+
(key) => key !== 'timestamp' && key !== 'level' && key !== 'message',
|
|
96
|
+
);
|
|
97
|
+
if (metaKeys.length > 0) {
|
|
98
|
+
output += `\n Meta: ${JSON.stringify(meta, null, 2)}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return output;
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private safeStringify(obj: any): string {
|
|
107
|
+
try {
|
|
108
|
+
return JSON.stringify(obj, (key, value) => {
|
|
109
|
+
if (typeof value === 'bigint') {
|
|
110
|
+
return value.toString();
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return '[Object cannot be serialized]';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private getTransports() {
|
|
120
|
+
return [
|
|
121
|
+
new transports.Console(),
|
|
122
|
+
new transports.File({ filename: 'logs/app.log' }),
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
log(
|
|
127
|
+
message: any,
|
|
128
|
+
{ level = 'info', ...rest }: { level?: string } & Record<string, any> = {},
|
|
129
|
+
) {
|
|
130
|
+
switch (level) {
|
|
131
|
+
case 'error':
|
|
132
|
+
this.logger.error(message, rest);
|
|
133
|
+
break;
|
|
134
|
+
case 'warn':
|
|
135
|
+
this.logger.warn(message, rest);
|
|
136
|
+
break;
|
|
137
|
+
case 'debug':
|
|
138
|
+
this.logger.debug(message, rest);
|
|
139
|
+
break;
|
|
140
|
+
case 'verbose':
|
|
141
|
+
this.logger.verbose(message, rest);
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
this.logger.info(message, rest);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
error(message: any, ...optionalParams: any[]) {
|
|
150
|
+
if (optionalParams.length > 0 && optionalParams[0] instanceof Error) {
|
|
151
|
+
const error = optionalParams[0];
|
|
152
|
+
const errorData: any = {
|
|
153
|
+
name: error.name,
|
|
154
|
+
message: error.message,
|
|
155
|
+
stack: error.stack,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
this.logger.error(message, {
|
|
159
|
+
error: errorData,
|
|
160
|
+
...optionalParams.slice(1),
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
this.logger.error(message, ...optionalParams);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
warn(message: any, ...optionalParams: any[]) {
|
|
168
|
+
this.logger.warn(message, ...optionalParams);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
debug(message: any, ...optionalParams: any[]) {
|
|
172
|
+
this.logger.debug(message, ...optionalParams);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
verbose(message: any, ...optionalParams: any[]) {
|
|
176
|
+
this.logger.verbose(message, ...optionalParams);
|
|
177
|
+
}
|
|
178
|
+
}
|