@truelab/trueserver 0.0.3 → 0.0.6

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 (132) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/MIGRATION.md +303 -0
  3. package/README.md +96 -98
  4. package/dist/application.d.ts.map +1 -1
  5. package/dist/application.js +9 -5
  6. package/dist/application.js.map +1 -1
  7. package/dist/examples.d.ts +7 -0
  8. package/dist/examples.d.ts.map +1 -0
  9. package/dist/examples.js +18 -0
  10. package/dist/examples.js.map +1 -0
  11. package/dist/helpers/config.helper.d.ts +4 -1
  12. package/dist/helpers/config.helper.d.ts.map +1 -1
  13. package/dist/helpers/config.helper.js +26 -0
  14. package/dist/helpers/config.helper.js.map +1 -1
  15. package/dist/helpers/errors.helper.d.ts +18 -0
  16. package/dist/helpers/errors.helper.d.ts.map +1 -0
  17. package/dist/helpers/errors.helper.js +20 -0
  18. package/dist/helpers/errors.helper.js.map +1 -0
  19. package/dist/helpers/fastify.helper.d.ts +5 -1
  20. package/dist/helpers/fastify.helper.d.ts.map +1 -1
  21. package/dist/helpers/fastify.helper.js +3 -1
  22. package/dist/helpers/fastify.helper.js.map +1 -1
  23. package/dist/helpers/handlers.helper.d.ts +10 -1
  24. package/dist/helpers/handlers.helper.d.ts.map +1 -1
  25. package/dist/helpers/handlers.helper.js +2 -1
  26. package/dist/helpers/handlers.helper.js.map +1 -1
  27. package/dist/index.d.ts +6 -4
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +7 -4
  30. package/dist/index.js.map +1 -1
  31. package/dist/plugins/404.plugin.d.ts.map +1 -1
  32. package/dist/plugins/404.plugin.js +2 -1
  33. package/dist/plugins/404.plugin.js.map +1 -1
  34. package/dist/plugins/apikey.plugin.d.ts.map +1 -1
  35. package/dist/plugins/apikey.plugin.js +6 -7
  36. package/dist/plugins/apikey.plugin.js.map +1 -1
  37. package/dist/plugins/error.plugin.d.ts.map +1 -1
  38. package/dist/plugins/error.plugin.js +6 -2
  39. package/dist/plugins/error.plugin.js.map +1 -1
  40. package/dist/plugins/sentry.plugin.js +2 -2
  41. package/dist/plugins/sentry.plugin.js.map +1 -1
  42. package/dist/plugins/swagger.plugin.d.ts.map +1 -1
  43. package/dist/plugins/swagger.plugin.js +5 -1
  44. package/dist/plugins/swagger.plugin.js.map +1 -1
  45. package/dist/routes/checksum/checksum.route.d.ts +3 -0
  46. package/dist/routes/checksum/checksum.route.d.ts.map +1 -0
  47. package/dist/routes/checksum/checksum.route.js +21 -0
  48. package/dist/routes/checksum/checksum.route.js.map +1 -0
  49. package/dist/routes/checksum/index.d.ts +3 -0
  50. package/dist/routes/checksum/index.d.ts.map +1 -0
  51. package/dist/routes/checksum/index.js +12 -0
  52. package/dist/routes/checksum/index.js.map +1 -0
  53. package/dist/routes/client/client.route.d.ts +3 -0
  54. package/dist/routes/client/client.route.d.ts.map +1 -0
  55. package/dist/routes/{client.route.js → client/client.route.js} +5 -5
  56. package/dist/routes/client/client.route.js.map +1 -0
  57. package/dist/routes/client/index.d.ts +3 -0
  58. package/dist/routes/client/index.d.ts.map +1 -0
  59. package/dist/routes/client/index.js +12 -0
  60. package/dist/routes/client/index.js.map +1 -0
  61. package/dist/routes/config/config.route.js +1 -1
  62. package/dist/routes/config/config.route.js.map +1 -1
  63. package/dist/routes/config/config.route.validate.plugin.d.ts.map +1 -1
  64. package/dist/routes/config/config.route.validate.plugin.js +43 -17
  65. package/dist/routes/config/config.route.validate.plugin.js.map +1 -1
  66. package/dist/routes/play/play.route.validate.plugin.d.ts +9 -9
  67. package/dist/routes/play/play.route.validate.plugin.d.ts.map +1 -1
  68. package/dist/routes/play/play.route.validate.plugin.js +84 -40
  69. package/dist/routes/play/play.route.validate.plugin.js.map +1 -1
  70. package/dist/routes/validate/validate.route.validate.plugin.d.ts.map +1 -1
  71. package/dist/routes/validate/validate.route.validate.plugin.js +9 -3
  72. package/dist/routes/validate/validate.route.validate.plugin.js.map +1 -1
  73. package/dist/routes-public/healthcheck.route.d.ts.map +1 -0
  74. package/dist/{routes → routes-public}/healthcheck.route.js +4 -10
  75. package/dist/routes-public/healthcheck.route.js.map +1 -0
  76. package/dist/schemas/checksum.schema.d.ts +11 -0
  77. package/dist/schemas/checksum.schema.d.ts.map +1 -0
  78. package/dist/schemas/checksum.schema.js +26 -0
  79. package/dist/schemas/checksum.schema.js.map +1 -0
  80. package/dist/schemas/client.schema.d.ts +1 -1
  81. package/dist/schemas/client.schema.js +4 -4
  82. package/dist/schemas/client.schema.js.map +1 -1
  83. package/dist/schemas/config.schema.d.ts +17 -20
  84. package/dist/schemas/config.schema.d.ts.map +1 -1
  85. package/dist/schemas/config.schema.js +41 -42
  86. package/dist/schemas/config.schema.js.map +1 -1
  87. package/dist/schemas/examples/checksum.schema.examples.d.ts +3 -0
  88. package/dist/schemas/examples/checksum.schema.examples.d.ts.map +1 -0
  89. package/dist/schemas/examples/checksum.schema.examples.js +26 -0
  90. package/dist/schemas/examples/checksum.schema.examples.js.map +1 -0
  91. package/dist/schemas/examples/client.schema.examples.d.ts.map +1 -1
  92. package/dist/schemas/examples/client.schema.examples.js +15 -18
  93. package/dist/schemas/examples/client.schema.examples.js.map +1 -1
  94. package/dist/schemas/examples/config.schema.examples.d.ts.map +1 -1
  95. package/dist/schemas/examples/config.schema.examples.js +30 -34
  96. package/dist/schemas/examples/config.schema.examples.js.map +1 -1
  97. package/dist/schemas/examples/healthcheck.schema.examples.d.ts.map +1 -1
  98. package/dist/schemas/examples/healthcheck.schema.examples.js +3 -14
  99. package/dist/schemas/examples/healthcheck.schema.examples.js.map +1 -1
  100. package/dist/schemas/examples/play.schema.examples.d.ts.map +1 -1
  101. package/dist/schemas/examples/play.schema.examples.js +66 -130
  102. package/dist/schemas/examples/play.schema.examples.js.map +1 -1
  103. package/dist/schemas/examples/validate.schema.examples.d.ts.map +1 -1
  104. package/dist/schemas/examples/validate.schema.examples.js +1 -3
  105. package/dist/schemas/examples/validate.schema.examples.js.map +1 -1
  106. package/dist/schemas/healthcheck.schema.d.ts +2 -20
  107. package/dist/schemas/healthcheck.schema.d.ts.map +1 -1
  108. package/dist/schemas/healthcheck.schema.js +10 -41
  109. package/dist/schemas/healthcheck.schema.js.map +1 -1
  110. package/dist/schemas/play.schema.d.ts +21 -16
  111. package/dist/schemas/play.schema.d.ts.map +1 -1
  112. package/dist/schemas/play.schema.js +36 -45
  113. package/dist/schemas/play.schema.js.map +1 -1
  114. package/dist/schemas/shared.d.ts +13 -21
  115. package/dist/schemas/shared.d.ts.map +1 -1
  116. package/dist/schemas/shared.js +56 -72
  117. package/dist/schemas/shared.js.map +1 -1
  118. package/dist/schemas/validate.schema.d.ts +2 -3
  119. package/dist/schemas/validate.schema.d.ts.map +1 -1
  120. package/dist/schemas/validate.schema.js +2 -5
  121. package/dist/schemas/validate.schema.js.map +1 -1
  122. package/package.json +10 -2
  123. package/dist/plugins/metrics.plugin.d.ts +0 -9
  124. package/dist/plugins/metrics.plugin.d.ts.map +0 -1
  125. package/dist/plugins/metrics.plugin.js +0 -24
  126. package/dist/plugins/metrics.plugin.js.map +0 -1
  127. package/dist/routes/client.route.d.ts +0 -3
  128. package/dist/routes/client.route.d.ts.map +0 -1
  129. package/dist/routes/client.route.js.map +0 -1
  130. package/dist/routes/healthcheck.route.d.ts.map +0 -1
  131. package/dist/routes/healthcheck.route.js.map +0 -1
  132. /package/dist/{routes → routes-public}/healthcheck.route.d.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.0.6] - 2026-06-03
10
+
11
+ ### Breaking
12
+ - `/config` reply: добавлено обязательное поле `rtpVersion: string` (лейбл RTP-бакета, совпадает с сегментом `{rtpVersion}` пути). См. [MIGRATION.md](MIGRATION.md).
13
+ - `TGameConfig.rtpVersion` — теперь обязательное поле для `registerGameConfig`.
14
+ - `Stake.amount` — семантика изменилась: теперь это **полная сумма ставки**, уже умноженная на `StakeMode.bets` и `qty`. Формула: `amount = baseBetAmount × StakeMode.bets × (qty ?? 1)`. Не валидируется пакетом — обновлены только описания.
15
+
16
+ ### Added
17
+ - `Stake.coinValue?: number` — опциональное поле для игр с coin-механикой (для рулеток и подобных — отсутствует).
18
+ - `config.route.validate.plugin` сверяет `rtpVersion` в трёх источниках: `request.params.rtpVersion`, `payload.rtpVersion`, `gameConfig.rtpVersion`. Несовпадение → 500 с `code=1006`.
19
+
20
+ ## [0.0.5] - 2026-05-29
21
+
22
+ ### Breaking
23
+ - Контракт API приведён к [GS Documentation 1.0.0](https://www.notion.so/truelab/GS-Documentation-36dd90f8c00680d485d4e4e3eba928f0). Полный список изменений и инструкция по переходу — в [MIGRATION.md](MIGRATION.md).
24
+ - Гейм-плей роуты под версионным префиксом `/:rtpVersion` (`/config`, `/client`, `/play`, `/validate`, `/checksum`). `/healthcheck` остаётся без версии.
25
+ - HTTP-методы: `GET /healthcheck`, `GET /:rtpVersion/config`, `GET /:rtpVersion/client`, `GET /:rtpVersion/checksum`. `/play` и `/validate` — `POST`.
26
+ - Обёртка ошибок: `{ status: false, code, message }` (вместо `{ ok: false, message }`).
27
+ - API-ключ проверяется на **всех** запросах (включая `GET`).
28
+ - `/play`: переименование полей (`demo` → `isDemo`, `winCash` → `cashWin`, `winCoins` → `coins`); `meta` — единый объект `{ public, private }`; `gameResponse` и `rng` в новом формате; `finished/choices/meta` перенесены в `results`. Добавлено обязательное `stepIndex`.
29
+ - `/config`: новая форма (`gameTitle`, `gameId`, `volatility: number`, `rtp: string`, `hasRestore`, `hasVariableLines`, `variableLevels: number[]`, `defaultLevel`). Удалены `category`, `hitrate`, `apiVersion`, `hasBuy`, `hasGamble`, `ECategory`, `EVolatility`.
30
+ - `/healthcheck`: ответ упрощён до `{ ok, gameId, environment, uptime, timestamp }`. Поле `service` → `gameId`. Удалены `metrics`, `services`, `code`, `status`, `message`.
31
+ - `/client`: убрано `ok`, добавлено опциональное поле `test` для cheat-tool.
32
+ - Удалён плагин `metrics`.
33
+
34
+ ### Added
35
+ - Новый эндпоинт `GET /:rtpVersion/checksum` для проверки целостности файлов.
36
+ - Хелпер `EErrorCode` + типизированный `httpError(message, statusCode, code)`.
37
+ - Инвариант `hasChoice ⇒ hasMeta` и `hasVariableLines ⇔ {variableLevels, defaultLevel}` проверяются в `registerGameConfig`.
38
+ - Экспорт `MetaSchema` / `TMeta`.
39
+
40
+ ## [Unreleased archive]
41
+
9
42
  ### Added
10
43
  - NPM-пакет `@truelab/trueserver` (exports, dist, prepack, publishConfig)
11
44
  - Bitbucket Pipelines: test / build / publish
package/MIGRATION.md ADDED
@@ -0,0 +1,303 @@
1
+ # Migration
2
+
3
+ - [0.0.5 → 0.0.6](#migration-005--006) (последнее)
4
+ - [0.0.4 → 0.0.5](#migration-004--005)
5
+
6
+ ---
7
+
8
+ ## Migration: 0.0.5 → 0.0.6
9
+
10
+ Документация GS Documentation обновлена после релиза 0.0.5. Изменения локальные, но breaking: новое обязательное поле в `/config` и опциональное в `Stake`.
11
+
12
+ > **TL;DR что менять в игре:**
13
+ > 1. В `registerGameConfig(...)` добавить `rtpVersion: '96'` (или ваш лейбл).
14
+ > 2. В `handlers.register('config', ...)` добавить `rtpVersion` в reply (тот же, что в `gameConfig`, и совпадающий с path-сегментом).
15
+ > 3. Опционально: добавить `coinValue` в `Stake` для игр с coin-механикой (запросы продолжают приниматься и без него).
16
+ > 4. Перепроверить `Stake.amount`: теперь это **полная сумма** (включая `StakeMode.bets` и `qty`), а не «на одну ставку».
17
+
18
+ ### 1. `/config` reply → новое обязательное `rtpVersion`
19
+
20
+ ```diff
21
+ {
22
+ "gameTitle": "Book of Odin",
23
+ "gameId": "book-of-odin",
24
+ "volatility": 2.5,
25
+ "rtp": "96.17",
26
+ + "rtpVersion": "96",
27
+ "hasMeta": true,
28
+ ...
29
+ }
30
+ ```
31
+
32
+ `rtpVersion` — лейбл RTP-бакета (`"96"`, `"97"`, …). Он:
33
+ - совпадает с сегментом `{rtpVersion}` пути, под которым смонтирован endpoint;
34
+ - 1:1 с `rtp` (одной RTP-версии — один точный `rtp` и один `rtpVersion`);
35
+ - сверяется с `gameConfig.rtpVersion` и с `request.params.rtpVersion`. Несовпадение → `500` с `code=1006`.
36
+
37
+ ### 2. `TGameConfig.rtpVersion`
38
+
39
+ ```diff
40
+ registerGameConfig({
41
+ gameTitle: 'My Game',
42
+ gameId: 'my-game',
43
+ + rtpVersion: '96',
44
+ ...
45
+ })
46
+ ```
47
+
48
+ `registerGameConfig` падает на старте, если `rtpVersion` пустой или не строка.
49
+
50
+ ### 3. `Stake.coinValue` (опционально)
51
+
52
+ ```diff
53
+ {
54
+ "name": "regular",
55
+ "amount": 1.0,
56
+ + "coinValue": 0.1,
57
+ "level": 9
58
+ }
59
+ ```
60
+
61
+ Игры без coin-механики (рулетки и т.п.) **не передают** это поле. Пакет не проверяет его наличие/отсутствие — формат остаётся на стороне игры.
62
+
63
+ ### 4. `Stake.amount` — новая семантика
64
+
65
+ Ничего в схеме менять не нужно, но **читать значение** теперь надо по-другому:
66
+
67
+ ```
68
+ amount = baseBetAmount × StakeMode.bets × (qty ?? 1)
69
+ ```
70
+
71
+ `baseBetAmount` — стоимость одной базовой ставки в валюте (`coinValue × coinRatio` для fixed-line; `coinValue × level` для variable-line).
72
+
73
+ `qty` **уже учтён** в `amount` — нельзя умножать ещё раз.
74
+
75
+ ### Чек-лист
76
+
77
+ 1. `npm install @truelab/trueserver@0.0.6`.
78
+ 2. Добавить `rtpVersion` в `registerGameConfig({...})`.
79
+ 3. Добавить `rtpVersion` в reply `handlers.register('config', ...)` — должен совпадать с тем, что в `gameConfig`, и с path-сегментом.
80
+ 4. Перепроверить расчёт `Stake.amount` в обработчике `/play`.
81
+ 5. Если используется coin-механика — добавить `coinValue` в формирование тестовых запросов / mocks.
82
+ 6. Прогнать тесты.
83
+
84
+ ---
85
+
86
+ ## Migration: 0.0.4 → 0.0.5
87
+
88
+ Этот документ описывает все изменения контракта между 0.0.4 и 0.0.5.
89
+ 0.0.5 приводит пакет в соответствие со спецификацией [GS Documentation 1.0.0](https://www.notion.so/truelab/GS-Documentation-36dd90f8c00680d485d4e4e3eba928f0).
90
+
91
+ Релиз помечен как **patch**, но **является ломающим**: меняются методы и пути роутов, формат ответов, имена и формы полей, удаляются enum'ы и флаги. Игры, использующие 0.0.4, не запустятся на 0.0.5 без правок.
92
+
93
+ > **TL;DR что менять в игре:**
94
+ > 1. `registerGameConfig(...)` — новые имена/набор полей.
95
+ > 2. `handlers.register('config', ...)` — новый ответ.
96
+ > 3. `handlers.register('play', ...)` — новый `request` и `reply`; в реквесте `request.params.rtpVersion` доступен.
97
+ > 4. `handlers.register('client', ...)` — убрать `ok: true`, добавить `test` если нужен cheat-tool.
98
+ > 5. Добавить `handlers.register('checksum', ...)`.
99
+ > 6. Везде, где раньше возвращали ошибку — теперь формат `{ status, code, message }`.
100
+
101
+ ## 1. Маршруты и методы
102
+
103
+ | Старое | Новое |
104
+ |---|---|
105
+ | `POST /healthcheck` | **`GET`** `/healthcheck` (без `:rtpVersion`) |
106
+ | `POST /config` | **`GET`** `/:rtpVersion/config` |
107
+ | `POST /client` | **`GET`** `/:rtpVersion/client` |
108
+ | — | **`GET`** `/:rtpVersion/checksum` (новый) |
109
+ | `POST /play` | `POST` `/:rtpVersion/play` |
110
+ | `POST /validate` | `POST` `/:rtpVersion/validate` |
111
+
112
+ - `{rtpVersion}` — путь-сегмент (`96`, `97`, `98` и т. п.). Игра получает его из `request.params.rtpVersion` (`TRtpParams` — см. `helpers/handlers.helper.ts`).
113
+ - `ROUTE_PREFIX` оборачивает оба варианта. Например, `ROUTE_PREFIX=/api/v1` даёт `GET /api/v1/healthcheck` и `POST /api/v1/96/play`.
114
+
115
+ ## 2. Аутентификация
116
+
117
+ API-ключ теперь проверяется на **всех** HTTP-методах (включая `GET`). Раньше — только на `POST`. Если у внешней инфраструктуры есть GET-пробы без заголовка `X-API-KEY`, их нужно обновить.
118
+
119
+ ## 3. Формат ошибок
120
+
121
+ Старое:
122
+
123
+ ```json
124
+ { "ok": false, "message": "...", "custom": null }
125
+ ```
126
+
127
+ Новое:
128
+
129
+ ```json
130
+ { "status": false, "code": 4001, "message": "..." }
131
+ ```
132
+
133
+ `code` — целое число. Диапазоны:
134
+
135
+ | Диапазон | Категория |
136
+ |---|---|
137
+ | `1xxx` | Валидация |
138
+ | `2xxx` | Аутентификация |
139
+ | `4xxx` | Бизнес-правила |
140
+ | `9xxx` | Internal / unhandled |
141
+
142
+ Константы — в `EErrorCode` (экспорт из корня пакета). Для собственных ошибок используйте `httpError(message, statusCode, code)`:
143
+
144
+ ```typescript
145
+ import { EErrorCode, httpError } from '@truelab/trueserver'
146
+
147
+ throw httpError('Unknown choice', 400, EErrorCode.BusinessChoiceUnknown)
148
+ ```
149
+
150
+ ## 4. `TGameConfig` (вход `registerGameConfig`)
151
+
152
+ | Старое | Новое |
153
+ |---|---|
154
+ | `name` | `gameTitle` |
155
+ | — | `gameId` (новое, общий ID для всех RTP) |
156
+ | `hasMeta` | `hasMeta` |
157
+ | `hasChoice` | `hasChoice` (теперь требует `hasMeta=true`) |
158
+ | `hasBuy` | — (удалено) |
159
+ | `hasGamble` | — (удалено) |
160
+ | — | `hasRestore` (новое) |
161
+ | `hasMultiStake` | `hasMultiStake` |
162
+ | `hasValidation` | `hasValidation` (опциональное, оставлено для `/validate`) |
163
+ | — | `hasVariableLines` (новое; раньше определялось по наличию `variableLevels`) |
164
+ | `variableLevels: number` (целое) | `variableLevels: number[]` (массив доступных уровней) |
165
+ | — | `defaultLevel: number` (обязателен при `hasVariableLines=true`) |
166
+ | `coinRatio?` | `coinRatio` (required при `hasVariableLines=false`) |
167
+
168
+ `registerGameConfig` теперь бросает при нарушении:
169
+ - `hasChoice=true` без `hasMeta=true`;
170
+ - `hasVariableLines=true` без непустого `variableLevels` или с `defaultLevel ∉ variableLevels`;
171
+ - `hasVariableLines=false` без `coinRatio`.
172
+
173
+ ## 5. `/config` reply
174
+
175
+ | Старое | Новое |
176
+ |---|---|
177
+ | `ok: true` | — (убрать, успех — plain JSON без обёртки) |
178
+ | `name` | `gameTitle` |
179
+ | — | `gameId` |
180
+ | `category: ECategory` | — (удалено) |
181
+ | `rtp: number` | `rtp: string` (десятичная строка, например `"96.17"`) |
182
+ | `volatility: EVolatility` (enum-строка, опционально) | `volatility: number` из `{0.5, 1, 1.5, …, 5}` (обязательное) |
183
+ | `hitrate?: number` | — (удалено) |
184
+ | `apiVersion: number` | — (удалено) |
185
+ | `hasBuy`, `hasGamble`, `hasValidation` | — (удалены из reply; `hasValidation` остался в `TGameConfig`) |
186
+ | — | `hasRestore: boolean` |
187
+ | — | `hasVariableLines: boolean` |
188
+ | `variableLevels: number` | `variableLevels?: number[]` |
189
+ | — | `defaultLevel?: number` |
190
+ | `minWin?: number` | `minWin: number` (обязательное, множитель ставки) |
191
+ | `maxWin: number` | `maxWin: number` (множитель ставки) |
192
+ | `coinRatio?: number` | `coinRatio?: number` |
193
+ | `stakeModes: StakeMode[]` | без изменений |
194
+
195
+ Удалены enum'ы: `ECategory`, `EVolatility`.
196
+
197
+ ## 6. `/play` request
198
+
199
+ | Старое | Новое |
200
+ |---|---|
201
+ | `demo: boolean` | `isDemo: boolean` |
202
+ | `currencyDecimals?: number` | `currencyDecimals: integer` (обязательное) |
203
+ | — | `stepIndex: integer` (новое; `0` для opening step, инкремент на каждое продолжение) |
204
+ | `stakes?: Stake[]` | `stakes?: Stake[]` — только на opening step; уникальные `name` |
205
+ | `choice?: integer` | `choice?: integer` — обязателен на continuation step при `hasChoice=true` |
206
+ | `metaPrivate?: string`, `metaPublic?: string` | `meta?: { public, private }` — один объект |
207
+ | `isGambleAllowed?: boolean` | — (удалено) |
208
+ | `test?: string` | `test?: string` — игнорируется в production |
209
+
210
+ ## 7. `/play` reply
211
+
212
+ | Старое | Новое |
213
+ |---|---|
214
+ | `ok: true` | — (убрать) |
215
+ | `rng: string` (stringified JSON) | `rng: Array<{ value: number, ... }>` |
216
+ | `finished: boolean` (top-level) | `results.finished: boolean` |
217
+ | `results.response: string` (stringified JSON) | `gameResponse: object` (top-level) |
218
+ | `results.winCash` | `results.cashWin` |
219
+ | `results.winCoins?` | `results.coins?` |
220
+ | `metaPrivate?`, `metaPublic?` (top-level, strings) | `results.meta?: { public, private }` |
221
+ | `choices?: number[]` (top-level) | `results.choices?: integer[]` (non-empty, не `null`/`[]`) |
222
+ | `analytics?` | — (удалено) |
223
+
224
+ Контракт `choices`: должен присутствовать тогда и только тогда, когда `hasChoice=true` и `results.finished=false`.
225
+
226
+ ## 8. `/client` reply
227
+
228
+ | Старое | Новое |
229
+ |---|---|
230
+ | `ok: true` | — (убрать) |
231
+ | `data: object` | `data: object` |
232
+ | — | `test?: object` (опциональное; RGS фильтрует для не-test-сессий) |
233
+
234
+ ## 9. `/healthcheck` reply
235
+
236
+ | Старое | Новое |
237
+ |---|---|
238
+ | `code: 200` | — |
239
+ | `status: true` | — |
240
+ | `service: string` | `gameId: string` |
241
+ | `environment` | `environment` |
242
+ | `uptime: number` | `uptime: number` |
243
+ | `message: 'OK'` | — |
244
+ | `timestamp: integer` | `timestamp: integer` (ms) |
245
+ | `metrics: {...}` | — (плагин `metrics` удалён) |
246
+ | `services: { sentry }` | — |
247
+ | — | `ok: true` (новое) |
248
+
249
+ ## 10. `/checksum` (новый эндпоинт)
250
+
251
+ `GET /:rtpVersion/checksum`:
252
+
253
+ ```json
254
+ {
255
+ "files": [
256
+ { "path": "src/math/probabilities.json", "hash": "9f86..." },
257
+ { "path": "src/math/paytable.json", "hash": "6c1b..." }
258
+ ]
259
+ }
260
+ ```
261
+
262
+ Алгоритм хэширования и набор файлов согласуются с RGS-командой при деплое.
263
+
264
+ ## 11. `/validate` (без изменений, но патч)
265
+
266
+ `/validate` сохранён как был (вне новой спеки) для обратной совместимости. Изменилось только:
267
+ - Reply теперь пустой объект `{}` (раньше `{ ok: true }`).
268
+ - Endpoint, как и остальные версионированные, доступен под `/:rtpVersion/validate`.
269
+
270
+ `hasValidation` остался во входе `registerGameConfig` как опциональный флаг. Если `false`, плагин валидации `/validate` отбивает запросы 400-кодом.
271
+
272
+ ## 12. Удалённые экспорты
273
+
274
+ - `ECategory`, `EVolatility` — удалены полностью.
275
+ - `TMetrics`, `MetricsSchema`, `metricsPlugin` — удалены.
276
+ - `winCash`/`winCoins`/`metaPrivate`/`metaPublic` в `/play` — удалены (см. п. 7).
277
+ - `apiVersion`, `hitrate`, `category`, `hasBuy`, `hasGamble` — удалены из `/config`.
278
+
279
+ ## 13. Что появилось
280
+
281
+ - `EErrorCode`, `TErrorCode` — нумерация ошибок.
282
+ - `MetaSchema`, `TMeta` — схема `{ public, private }`.
283
+ - `ChecksumReplySchema`, `ChecksumRequestSchema`, `TChecksumReply`, `TChecksumRequest`.
284
+ - `TRtpParams = { rtpVersion: string }` — тип path-параметра.
285
+ - `FastifyError$Coded` — расширение `FastifyError` с полем `errorCode`.
286
+ - `httpError(message, statusCode, code)` — третий параметр для машинного кода ошибки.
287
+
288
+ ## 14. Чек-лист обновления игры
289
+
290
+ 1. `npm install @truelab/trueserver@0.0.5`.
291
+ 2. Привести `registerGameConfig({...})` к новой форме (см. п. 4).
292
+ 3. Переписать `handlers.register('config', ...)` под новый reply (см. п. 5).
293
+ 4. Переписать `handlers.register('play', ...)` — request/reply (см. пп. 6, 7). `meta` теперь объект, а не два string'а.
294
+ 5. В `handlers.register('client', ...)` убрать `ok: true`. При необходимости добавить поле `test`.
295
+ 6. Зарегистрировать `handlers.register('checksum', ...)` (если интегрируется проверка целостности).
296
+ 7. Если был `handlers.register('validate', ...)` с `ok: true` — теперь возвращать `{}`.
297
+ 8. Сменить все собственные ошибки на формат `{ status, code, message }` или использовать `httpError(..., code)`.
298
+ 9. Обновить тесты: пути с `/:rtpVersion`, методы GET для `/config`/`/client`/`/checksum`, GET для `/healthcheck`, новые имена полей.
299
+ 10. Обновить URL-ы на стороне RGS-конфига для каждой игры.
300
+
301
+ ## Вопросы
302
+
303
+ Регрессии и нерешённые ситуации — в Slack `#truelab-games-platform` или к командe RGS.
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # @truelab/trueserver
2
2
 
3
- Fastify-based server package for TrueLab games.
3
+ Fastify-based server package for TrueLab games. Implements the **Game Server** contract described in [GS Documentation](https://www.notion.so/truelab/GS-Documentation-36dd90f8c00680d485d4e4e3eba928f0).
4
4
 
5
- The package provides ready-to-use HTTP routes, request/response validation, shared TypeBox schemas, API key checks, Sentry integration, and Swagger UI for local development. A game server only needs to register game metadata and route handlers.
5
+ The package provides HTTP routes, TypeBox request/response schemas, API-key authentication, error envelope, Sentry integration and Swagger UI. A game team only needs to call `registerGameConfig`, register handlers and start the server.
6
6
 
7
7
  ## Requirements
8
8
 
9
- - Node.js 22.12+ (uses `require()` of an ESM-only `typebox` dependency)
10
- - npm package access for `@truelab/trueserver`
9
+ - Node.js 22.12+
10
+ - npm access to `@truelab/trueserver`
11
11
 
12
12
  ## Installation
13
13
 
@@ -17,18 +17,15 @@ npm install @truelab/trueserver
17
17
 
18
18
  ## Quick Start
19
19
 
20
- Register game config and handlers before building the Fastify app:
21
-
22
20
  ```typescript
23
21
  import {
24
22
  Application,
25
- ECategory,
26
23
  EStakeType,
27
- EVolatility,
28
24
  handlers,
29
25
  getenv,
30
26
  logger,
31
27
  registerGameConfig,
28
+ type TChecksumReply,
32
29
  type TClientReply,
33
30
  type TConfigReply,
34
31
  type TPlayReply,
@@ -44,57 +41,61 @@ const stakeModes = [{
44
41
  }]
45
42
 
46
43
  registerGameConfig({
47
- name: 'My Game',
44
+ gameTitle: 'My Game',
45
+ gameId: 'my-game',
46
+ rtpVersion: '96',
48
47
  stakeModes,
49
- hasBuy: false,
50
- hasChoice: false,
51
- hasGamble: false,
52
48
  hasMeta: false,
49
+ hasChoice: false,
50
+ hasRestore: false,
53
51
  hasMultiStake: false,
54
- hasValidation: false,
52
+ hasVariableLines: false,
55
53
  coinRatio: 10,
56
54
  })
57
55
 
58
- handlers.register('client', async (): Promise<TClientReply> => ({
59
- ok: true,
60
- data: {},
61
- }))
62
-
63
56
  handlers.register('config', async (): Promise<TConfigReply> => ({
64
- ok: true,
65
- name: 'My Game',
66
- category: ECategory.slots,
67
- stakeModes,
68
- rtp: 96,
69
- hitrate: 25,
70
- volatility: EVolatility.med,
57
+ gameTitle: 'My Game',
58
+ gameId: 'my-game',
59
+ volatility: 2.5,
60
+ rtp: '96.17',
61
+ rtpVersion: '96',
71
62
  hasMeta: false,
72
63
  hasChoice: false,
73
- hasBuy: false,
64
+ hasRestore: false,
74
65
  hasMultiStake: false,
75
- hasValidation: false,
76
- hasGamble: false,
77
- freeroundsAllowed: false,
66
+ hasVariableLines: false,
67
+ coinRatio: 10,
78
68
  minWin: 0.1,
79
69
  maxWin: 5000,
80
- apiVersion: 0,
81
- coinRatio: 10,
70
+ freeroundsAllowed: false,
71
+ stakeModes,
82
72
  }))
83
73
 
84
- handlers.register('play', async (): Promise<TPlayReply> => ({
85
- ok: true,
86
- rng: '[]',
87
- finished: true,
88
- results: {
89
- response: '{}',
90
- winCash: 0,
91
- },
74
+ handlers.register('client', async (): Promise<TClientReply> => ({
75
+ data: {},
92
76
  }))
93
77
 
94
- handlers.register('validate', async (): Promise<TValidateReply> => ({
95
- ok: true,
78
+ handlers.register('play', async (request): Promise<TPlayReply> => {
79
+ // request.params.rtpVersion is available for routing math by RTP
80
+ return {
81
+ gameResponse: {},
82
+ rng: [],
83
+ results: {
84
+ cashWin: 0,
85
+ finished: true,
86
+ },
87
+ }
88
+ })
89
+
90
+ handlers.register('checksum', async (): Promise<TChecksumReply> => ({
91
+ files: [
92
+ { path: 'src/math/spin.js', hash: '...' },
93
+ ],
96
94
  }))
97
95
 
96
+ // Optional. `hasValidation` is a legacy flag — kept for backward compatibility.
97
+ handlers.register('validate', async (): Promise<TValidateReply> => ({}))
98
+
98
99
  async function main() {
99
100
  const app = new Application()
100
101
  const server = await app.build()
@@ -111,63 +112,74 @@ main().catch((error) => {
111
112
  })
112
113
  ```
113
114
 
114
- `registerGameConfig` must be called before `app.build()`: validation plugins read this config while the server is being built.
115
+ `registerGameConfig` must be called before `app.build()` validation plugins read the config while the server is being built. The function throws if invariants are violated (e.g. `hasChoice=true` without `hasMeta=true`, or `hasVariableLines=true` without `variableLevels`/`defaultLevel`).
115
116
 
116
117
  ## Runtime API
117
118
 
118
- The server registers these routes:
119
+ `{rtpVersion}` is a path segment (e.g. `96`, `97`, `98`) — RGS picks the correct RTP per call. Game Server receives it as `request.params.rtpVersion`.
119
120
 
120
121
  | Method | Path | Handler | Description |
121
122
  | ------ | ---- | ------- | ----------- |
122
- | `POST` | `/client` | `handlers.register('client', ...)` | Client settings payload |
123
- | `POST` | `/config` | `handlers.register('config', ...)` | Public game config |
124
- | `POST` | `/play` | `handlers.register('play', ...)` | Game round calculation |
125
- | `POST` | `/validate` | `handlers.register('validate', ...)` | Optional stake/meta validation |
126
- | `POST` | `/healthcheck` | built in | Service status and metrics |
123
+ | `GET` | `/healthcheck` | built-in | Liveness/readiness probe |
124
+ | `GET` | `/:rtpVersion/config` | `handlers.register('config', ...)` | Game config (RTP-specific) |
125
+ | `GET` | `/:rtpVersion/client` | `handlers.register('client', ...)` | Client-side payload |
126
+ | `GET` | `/:rtpVersion/checksum` | `handlers.register('checksum', ...)` | File hashes for integrity check |
127
+ | `POST` | `/:rtpVersion/play` | `handlers.register('play', ...)` | One step of a round |
128
+ | `POST` | `/:rtpVersion/validate` | `handlers.register('validate', ...)` | Legacy stake/meta pre-validation (off by default) |
127
129
 
128
- All `POST` routes require an API key header. By default the server expects `x-api-key: test`; configure it with `API_HEADER` and `API_KEY`.
130
+ Every request must carry the API key header. By default it is `x-api-key: test`; override with `API_HEADER` and `API_KEY`.
129
131
 
130
- `ROUTE_PREFIX` is applied to all routes. For example, `ROUTE_PREFIX=/api/game` turns `/play` into `/api/game/play`.
132
+ `ROUTE_PREFIX` is prepended to every route. For `ROUTE_PREFIX=/api/v1`, the play endpoint becomes `POST /api/v1/96/play`. `/healthcheck` is always mounted directly under the prefix (no `{rtpVersion}` segment).
131
133
 
132
- ## Imports
134
+ ## Error envelope
133
135
 
134
- Use the root entry for runtime code, schemas, enums, helpers, and public types:
136
+ Non-2xx responses follow:
137
+
138
+ ```json
139
+ {
140
+ "status": false,
141
+ "code": 4001,
142
+ "message": "Invalid choice value"
143
+ }
144
+ ```
145
+
146
+ Recommended code ranges: `1xxx` validation, `2xxx` auth, `4xxx` business, `9xxx` internal. The `EErrorCode` enum exports the codes used by this package.
135
147
 
136
148
  ```typescript
137
- import {
138
- Application,
139
- EStakeType,
140
- PlayRequestSchema,
141
- handlers,
142
- registerGameConfig,
143
- type TPlayReply,
144
- } from '@truelab/trueserver'
149
+ import { EErrorCode, httpError } from '@truelab/trueserver'
150
+
151
+ throw httpError('Invalid choice value', 400, EErrorCode.BusinessChoiceUnknown)
145
152
  ```
146
153
 
147
- Test helpers are intentionally published through a separate entry:
154
+ ## Test utilities
148
155
 
149
156
  ```typescript
150
- import { testBuild } from '@truelab/trueserver/test-utils'
157
+ import {
158
+ checkSchemaPropertyDescriptions,
159
+ testBuild,
160
+ testHeaders,
161
+ validator,
162
+ } from '@truelab/trueserver/test-utils'
151
163
  ```
152
164
 
153
- Published code is compiled into `dist/`; package `exports` exposes only the public root entry and test utilities.
165
+ `test-utils` uses Jest globals (`expect`), so consumers should keep `jest` and usually `@types/jest` in `devDependencies`.
154
166
 
155
- ## Environment Variables
167
+ ## Environment variables
156
168
 
157
169
  | Variable | Default | Description |
158
170
  | -------- | ------- | ----------- |
159
- | `HOST` | `localhost` | Listen host used by the consuming game server |
160
- | `PORT` | `2980` | Listen port used by the consuming game server |
161
- | `ROUTE_PREFIX` | empty | Prefix for all autoloaded routes |
162
- | `API_HEADER` | `x-api-key` | Header name used by the API key plugin |
163
- | `API_KEY` | `test` | Expected API key for `POST` requests |
171
+ | `HOST` | `localhost` | Listen host |
172
+ | `PORT` | `2980` | Listen port |
173
+ | `ROUTE_PREFIX` | empty | Prefix for all routes |
174
+ | `API_HEADER` | `x-api-key` | Header name used by API-key plugin |
175
+ | `API_KEY` | `test` | Expected API key value |
164
176
  | `LOG_LEVEL` | `error` | Pino log level |
165
- | `APP_ENV` | unset | `dev`, `release`, or `master`; controls Swagger and environment mapping |
177
+ | `APP_ENV` | unset | `dev`, `release`, `master`; controls Swagger and environment mapping |
166
178
  | `SENTRY_DSN` | unset | Sentry DSN |
167
179
  | `SENTRY_RATE` | `0.01` | Sentry traces sample rate |
168
- | `GAME_ID` | `0001` | Sentry `game.id` tag |
180
+ | `GAME_ID` | `0001` | Sentry `game.id` tag fallback |
169
181
 
170
- The environment helper imports `dotenv/config`, so `.env` is loaded when `@truelab/trueserver` is imported.
182
+ The environment helper imports `dotenv/config`, so `.env` is loaded automatically.
171
183
 
172
184
  ## Swagger
173
185
 
@@ -175,45 +187,31 @@ Swagger UI is enabled only for `APP_ENV=dev`.
175
187
 
176
188
  - `GET /` redirects to `/docs`
177
189
  - `GET /docs` serves Swagger UI
178
- - `release` and `master` disable Swagger UI
179
190
 
180
- When `ROUTE_PREFIX` is set, it affects game routes, not the Swagger UI route.
191
+ `release` and `master` disable Swagger UI.
181
192
 
182
193
  ## Advanced Validation
183
194
 
184
- The package validates both requests and successful replies with TypeBox schemas. It also performs game-specific checks based on `registerGameConfig`.
195
+ The package validates both requests and replies via TypeBox schemas and runs game-specific checks based on `registerGameConfig`:
185
196
 
186
- - `/config` checks stake modes and requires exactly one of `coinRatio` or `variableLevels`.
187
- - `/play` checks stakes, choices, meta data, gamble flag, and reply consistency.
188
- - `/validate` is available only when `hasValidation` is `true`.
189
- - `test` data is rejected in production mapping (`APP_ENV=master` or unset).
197
+ - `/config` checks stake modes, the `hasChoice hasMeta` invariant and the `hasVariableLines {variableLevels, defaultLevel}` pair.
198
+ - `/play` checks stakes (uniqueness, qty, level in `variableLevels`, parametric/params), `stepIndex` consistency (opening vs continuation), `choice`/`meta` presence per flags, and reply shape (`finished`, `choices`, `meta`).
199
+ - `/validate` is available only when `hasValidation=true`.
200
+ - `test` field is rejected on production (`APP_ENV=master` or unset).
190
201
 
191
- For stepped games, set `hasChoice: true`; for games with meta state, set `hasMeta: true`; for line/table variants, use `variableLevels` instead of `coinRatio`.
192
-
193
- ## Test Utilities
194
-
195
- Integration tests in consuming game servers can import Jest helpers from:
196
-
197
- ```typescript
198
- import {
199
- checkSchemaPropertyDescriptions,
200
- testBuild,
201
- testHeaders,
202
- validator,
203
- } from '@truelab/trueserver/test-utils'
204
- ```
202
+ ## Migration
205
203
 
206
- `test-utils` uses Jest globals (`expect`), so the consuming project should keep `jest` and usually `@types/jest` in `devDependencies`.
204
+ This release is breaking. See [MIGRATION.md](MIGRATION.md) for a full mapping of old fields/paths to new ones.
207
205
 
208
206
  ## Package Development
209
207
 
210
208
  ```bash
211
- npm test # run Jest tests
212
- npm run build # compile TypeScript to dist/
213
- npm pack # build and inspect the npm tarball
209
+ npm test
210
+ npm run build
211
+ npm pack
214
212
  ```
215
213
 
216
- `bitbucket-pipelines.yml` runs tests and build on the default branch. The custom `publish` pipeline publishes to npm with `NPM_PUBLISH_TOKEN`.
214
+ `bitbucket-pipelines.yml` runs tests and build on the default branch.
217
215
 
218
216
  ## License
219
217
 
@@ -1 +1 @@
1
- {"version":3,"file":"application.d.ts","sourceRoot":"","sources":["../application.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAkB,MAAM,0BAA0B,CAAA;AAGzE,MAAM,CAAC,OAAO,OAAO,WAAW;IAC5B,MAAM,EAAE,cAAc,CAAA;;IAchB,KAAK,IAAI,OAAO,CAAC,cAAc,CAAC;CAiBzC"}
1
+ {"version":3,"file":"application.d.ts","sourceRoot":"","sources":["../application.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAkB,MAAM,0BAA0B,CAAA;AAGzE,MAAM,CAAC,OAAO,OAAO,WAAW;IAC5B,MAAM,EAAE,cAAc,CAAA;;IAchB,KAAK,IAAI,OAAO,CAAC,cAAc,CAAC;CAuBzC"}