@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.
Files changed (146) hide show
  1. package/README.md +15 -15
  2. package/dist/__tests__/bonnys-fortune-v1.game-engine.test.d.ts +1 -0
  3. package/dist/bonnys-fortune-v1.game-engine.d.ts +36 -0
  4. package/dist/bonnys-fortune-v1.game-engine.js +23 -12
  5. package/dist/bonnys-fortune-v1.game-engine.js.map +1 -1
  6. package/dist/config/game-logic-config/file-system.game-logic-config-loader.d.ts +4 -0
  7. package/dist/config/game-logic-config/game-logic-config-loader.d.ts +3 -0
  8. package/dist/config/game-logic-config/game-logic-config.d.ts +2 -0
  9. package/dist/config/game-logic-config/game-logic-config.js +2 -0
  10. package/dist/config/game-logic-config/game-logic-config.js.map +1 -1
  11. package/dist/config/reel-strips-config/file-system.reel-strips-config-loader.d.ts +4 -0
  12. package/dist/config/reel-strips-config/reel-strip-option.dto.d.ts +4 -0
  13. package/dist/config/reel-strips-config/reel-strips-config-loader.d.ts +3 -0
  14. package/dist/config/reel-strips-config/reel-strips-config.dto.d.ts +5 -0
  15. package/dist/config/reel-strips-config/reel.dto.d.ts +5 -0
  16. package/dist/domain/game-round.types.d.ts +9 -0
  17. package/dist/domain/mappers/reel-strips-config.mapper.d.ts +4 -0
  18. package/dist/domain/reel-strip-option.d.ts +7 -0
  19. package/dist/domain/reel-strips-config.d.ts +9 -0
  20. package/dist/domain/reel.d.ts +8 -0
  21. package/dist/domain/types/cheat-trigger-bonus-command.d.ts +5 -0
  22. package/dist/domain/types/{debug-trigger-bonus-command.js → cheat-trigger-bonus-command.js} +1 -1
  23. package/dist/domain/types/cheat-trigger-bonus-command.js.map +1 -0
  24. package/dist/domain/types/cheat-trigger-bonus-request.dto.d.ts +5 -0
  25. package/dist/domain/types/{debug-trigger-bonus-request.dto.js → cheat-trigger-bonus-request.dto.js} +1 -1
  26. package/dist/domain/types/cheat-trigger-bonus-request.dto.js.map +1 -0
  27. package/dist/domain/types/cheat-update-bonus-meter-progress-command.d.ts +5 -0
  28. package/dist/domain/types/{debug-update-bonus-meter-progress-command.js → cheat-update-bonus-meter-progress-command.js} +1 -1
  29. package/dist/domain/types/cheat-update-bonus-meter-progress-command.js.map +1 -0
  30. package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.d.ts +5 -0
  31. package/dist/domain/types/{debug-update-bonus-meter-progress-request.dto.js → cheat-update-bonus-meter-progress-request.dto.js} +1 -1
  32. package/dist/domain/types/cheat-update-bonus-meter-progress-request.dto.js.map +1 -0
  33. package/dist/domain/types/game-spin-command.d.ts +8 -0
  34. package/dist/domain/types/game-spin-input.dto.d.ts +8 -0
  35. package/dist/domain/types/game-spin-output.dto.d.ts +119 -0
  36. package/dist/domain/types/game-symbols.response.dto.d.ts +10 -0
  37. package/dist/domain/types/reel-strip-option.dto.d.ts +4 -0
  38. package/dist/domain/types/reel-strips-config.dto.d.ts +5 -0
  39. package/dist/domain/types/reel.dto.d.ts +5 -0
  40. package/dist/domain/types/start-bonus-round-command.d.ts +8 -0
  41. package/dist/domain/types/start-bonus-round-input.dto.d.ts +10 -0
  42. package/dist/game-engine.interface.d.ts +41 -0
  43. package/dist/helpers/generate-hash.d.ts +1 -0
  44. package/dist/helpers/number-helper.d.ts +23 -0
  45. package/dist/helpers/optional-boolean-mapper.d.ts +1 -0
  46. package/dist/helpers/uuid-helper.d.ts +3 -0
  47. package/dist/helpers/validation-helper.d.ts +14 -0
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/logger/logger.d.ts +22 -0
  51. package/dist/logger/logger.js +4 -5
  52. package/dist/logger/logger.js.map +1 -1
  53. package/dist/logic/bonnys-fortune.game-logic.d.ts +32 -0
  54. package/dist/logic/bonnys-fortune.game-logic.js +15 -11
  55. package/dist/logic/bonnys-fortune.game-logic.js.map +1 -1
  56. package/dist/logic/bonnys-fortune.spin-generator.d.ts +19 -0
  57. package/dist/logic/bonnys-fortune.types.d.ts +170 -0
  58. package/dist/logic/bonnys-fortune.win-calculator.d.ts +10 -0
  59. package/dist/logic/game-logic-config.interface.d.ts +180 -0
  60. package/dist/logic/handlers/base-game.handler.d.ts +15 -0
  61. package/dist/logic/handlers/base-game.handler.js +5 -1
  62. package/dist/logic/handlers/base-game.handler.js.map +1 -1
  63. package/dist/logic/handlers/collect-feature-bonus.handler.d.ts +13 -0
  64. package/dist/logic/handlers/free-spins-bonus.handler.d.ts +11 -0
  65. package/dist/logic/handlers/steering-to-the-fortune-bonus.handler.d.ts +11 -0
  66. package/dist/logic/handlers/treasure-hunt-bonus.handler.d.ts +15 -0
  67. package/dist/rng/DummyRngClient.d.ts +18 -0
  68. package/dist/rng/DummyRngClient.js +1 -8
  69. package/dist/rng/DummyRngClient.js.map +1 -1
  70. package/dist/rng/rng-client.factory.d.ts +7 -0
  71. package/dist/rng/rng-client.factory.js.map +1 -1
  72. package/dist/rng/rng-client.interface.d.ts +16 -0
  73. package/dist/rng/rng-service.d.ts +26 -0
  74. package/dist/validation/abstract-game-logic-config.validator.d.ts +4 -0
  75. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.d.ts +131 -0
  76. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js +9 -0
  77. package/dist/validation/bonnys-fortune/bonnys-fortune-config.dto.js.map +1 -1
  78. package/dist/validation/bonnys-fortune/bonnys-fortune-config.validator.d.ts +5 -0
  79. package/dist/validation/custom-decorators/IsNestedIntArray.d.ts +2 -0
  80. package/dist/validation/game-logic-config-validation.service.d.ts +8 -0
  81. package/dist/validation/reel-strips-config-validation.service.d.ts +5 -0
  82. package/package.json +4 -3
  83. package/src/__tests__/bonnys-fortune-v1.game-engine.test.ts +80 -0
  84. package/src/bonnys-fortune-v1.game-engine.ts +314 -0
  85. package/src/config/game-logic-config/file-system.game-logic-config-loader.ts +27 -0
  86. package/src/config/game-logic-config/game-logic-config-loader.ts +3 -0
  87. package/src/config/game-logic-config/game-logic-config.ts +382 -0
  88. package/src/config/reel-strips-config/file-system.reel-strips-config-loader.ts +43 -0
  89. package/src/config/reel-strips-config/reel-strip-option.dto.ts +13 -0
  90. package/src/config/reel-strips-config/reel-strips-config-loader.ts +9 -0
  91. package/src/config/reel-strips-config/reel-strips-config.dto.ts +16 -0
  92. package/src/config/reel-strips-config/reel.dto.ts +16 -0
  93. package/src/config/reel-strips-config/reels-BASE.csv +52 -0
  94. package/src/config/reel-strips-config/reels-BONUS.csv +52 -0
  95. package/src/domain/game-round.types.ts +10 -0
  96. package/src/domain/mappers/reel-strips-config.mapper.ts +16 -0
  97. package/src/domain/reel-strip-option.ts +15 -0
  98. package/src/domain/reel-strips-config.ts +21 -0
  99. package/src/domain/reel.ts +17 -0
  100. package/src/domain/types/cheat-trigger-bonus-command.ts +5 -0
  101. package/src/domain/types/cheat-trigger-bonus-request.dto.ts +5 -0
  102. package/src/domain/types/cheat-update-bonus-meter-progress-command.ts +6 -0
  103. package/src/domain/types/cheat-update-bonus-meter-progress-request.dto.ts +5 -0
  104. package/src/domain/types/game-spin-command.ts +8 -0
  105. package/src/domain/types/game-spin-input.dto.ts +8 -0
  106. package/src/domain/types/game-spin-output.dto.ts +142 -0
  107. package/src/domain/types/game-symbols.response.dto.ts +11 -0
  108. package/src/domain/types/reel-strip-option.dto.ts +13 -0
  109. package/src/domain/types/reel-strips-config.dto.ts +15 -0
  110. package/src/domain/types/reel.dto.ts +15 -0
  111. package/src/domain/types/start-bonus-round-command.ts +8 -0
  112. package/src/domain/types/start-bonus-round-input.dto.ts +10 -0
  113. package/src/game-engine.interface.ts +59 -0
  114. package/src/helpers/generate-hash.ts +5 -0
  115. package/src/helpers/number-helper.ts +41 -0
  116. package/src/helpers/optional-boolean-mapper.ts +5 -0
  117. package/src/helpers/uuid-helper.ts +7 -0
  118. package/src/helpers/validation-helper.ts +27 -0
  119. package/src/index.ts +3 -0
  120. package/src/logger/logger.ts +178 -0
  121. package/src/logic/bonnys-fortune.game-logic.ts +490 -0
  122. package/src/logic/bonnys-fortune.spin-generator.ts +277 -0
  123. package/src/logic/bonnys-fortune.types.ts +223 -0
  124. package/src/logic/bonnys-fortune.win-calculator.ts +210 -0
  125. package/src/logic/game-logic-config.interface.ts +176 -0
  126. package/src/logic/handlers/base-game.handler.ts +221 -0
  127. package/src/logic/handlers/collect-feature-bonus.handler.ts +301 -0
  128. package/src/logic/handlers/free-spins-bonus.handler.ts +119 -0
  129. package/src/logic/handlers/steering-to-the-fortune-bonus.handler.ts +118 -0
  130. package/src/logic/handlers/treasure-hunt-bonus.handler.ts +232 -0
  131. package/src/rng/DummyRngClient.ts +108 -0
  132. package/src/rng/rng-client.factory.ts +27 -0
  133. package/src/rng/rng-client.interface.ts +38 -0
  134. package/src/rng/rng-service.ts +130 -0
  135. package/src/validation/abstract-game-logic-config.validator.ts +20 -0
  136. package/src/validation/bonnys-fortune/bonnys-fortune-config.dto.ts +379 -0
  137. package/src/validation/bonnys-fortune/bonnys-fortune-config.validator.ts +8 -0
  138. package/src/validation/custom-decorators/IsNestedIntArray.ts +29 -0
  139. package/src/validation/game-logic-config-validation.service.ts +28 -0
  140. package/src/validation/reel-strips-config-validation.service.ts +29 -0
  141. package/dist/__tests__/comprehensive.test.js +0 -741
  142. package/dist/__tests__/comprehensive.test.js.map +0 -1
  143. package/dist/domain/types/debug-trigger-bonus-command.js.map +0 -1
  144. package/dist/domain/types/debug-trigger-bonus-request.dto.js.map +0 -1
  145. package/dist/domain/types/debug-update-bonus-meter-progress-command.js.map +0 -1
  146. package/dist/domain/types/debug-update-bonus-meter-progress-request.dto.js.map +0 -1
@@ -0,0 +1,8 @@
1
+ export type GameSpinCommand = {
2
+ sessionId: string;
3
+ betAmount: number;
4
+ gameCode: string;
5
+ gameVersion: string;
6
+ lines?: number;
7
+ creditsPerLine?: number;
8
+ };
@@ -0,0 +1,8 @@
1
+ export interface GameSpinInputDto {
2
+ sessionId: string;
3
+ betAmount: number;
4
+ gameCode: string;
5
+ gameVersion: string;
6
+ lines?: number;
7
+ creditsPerLine?: number;
8
+ }
@@ -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,11 @@
1
+ export interface GameSymbol {
2
+ symbolId: number;
3
+ name: string;
4
+ multipliers: Record<'x3' | 'x4' | 'x5', number>;
5
+ isWild: boolean;
6
+ isScatter: boolean;
7
+ }
8
+
9
+ export class GameSymbolsResponseDto {
10
+ public symbols: GameSymbol[];
11
+ }
@@ -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,8 @@
1
+ export type StartBonusRoundCommand = {
2
+ sessionId: string;
3
+ bonusId: string;
4
+ bonusType: string;
5
+ betAmount: number;
6
+ gameCode: string;
7
+ gameVersion: string;
8
+ };
@@ -0,0 +1,10 @@
1
+ export interface StartBonusRoundInputDto {
2
+ sessionId: string;
3
+ bonusType: string;
4
+ betAmount: number;
5
+ gameCode: string;
6
+ gameVersion: string;
7
+ lines?: number;
8
+ creditsPerLine?: number;
9
+ bonusId: string;
10
+ }
@@ -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,5 @@
1
+ import { createHash } from 'crypto';
2
+
3
+ export const generateHash = (input: string): string => {
4
+ return createHash('sha256').update(input).digest('hex');
5
+ };
@@ -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,5 @@
1
+ export const optionalBooleanMapper = new Map([
2
+ ['undefined', undefined],
3
+ ['true', true],
4
+ ['false', false],
5
+ ]);
@@ -0,0 +1,7 @@
1
+ import { v4 } from 'uuid';
2
+
3
+ export class UuidHelper {
4
+ public static generateUuid(): string {
5
+ return v4();
6
+ }
7
+ }
@@ -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,3 @@
1
+ import 'reflect-metadata';
2
+
3
+ export { BonnysFortuneV1GameEngine } from './bonnys-fortune-v1.game-engine';
@@ -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
+ }