volleyballsimtypes 0.0.491 → 0.0.493

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 (135) hide show
  1. package/dist/cjs/src/api/index.d.ts +2 -1
  2. package/dist/cjs/src/api/index.js +15 -0
  3. package/dist/cjs/src/api/tactics-player-refs.d.ts +13 -0
  4. package/dist/cjs/src/api/tactics-player-refs.js +43 -0
  5. package/dist/cjs/src/data/event-purge.d.ts +14 -0
  6. package/dist/cjs/src/data/event-purge.js +116 -0
  7. package/dist/cjs/src/data/index.d.ts +1 -0
  8. package/dist/cjs/src/data/index.js +1 -0
  9. package/dist/cjs/src/data/init-models.js +41 -0
  10. package/dist/cjs/src/data/models/competition.d.ts +1 -1
  11. package/dist/cjs/src/data/models/competition.js +1 -1
  12. package/dist/cjs/src/data/models/event-bot-team.d.ts +21 -0
  13. package/dist/cjs/src/data/models/event-bot-team.js +44 -0
  14. package/dist/cjs/src/data/models/event-competition.d.ts +29 -0
  15. package/dist/cjs/src/data/models/event-competition.js +52 -0
  16. package/dist/cjs/src/data/models/event-pack-player.d.ts +22 -0
  17. package/dist/cjs/src/data/models/event-pack-player.js +31 -0
  18. package/dist/cjs/src/data/models/event-pack.d.ts +37 -0
  19. package/dist/cjs/src/data/models/event-pack.js +55 -0
  20. package/dist/cjs/src/data/models/event-result.d.ts +34 -0
  21. package/dist/cjs/src/data/models/event-result.js +48 -0
  22. package/dist/cjs/src/data/models/event-squad.d.ts +30 -0
  23. package/dist/cjs/src/data/models/event-squad.js +46 -0
  24. package/dist/cjs/src/data/models/event.d.ts +51 -0
  25. package/dist/cjs/src/data/models/event.js +82 -0
  26. package/dist/cjs/src/data/models/index.d.ts +7 -0
  27. package/dist/cjs/src/data/models/index.js +7 -0
  28. package/dist/cjs/src/data/models/lineup-preset.d.ts +3 -1
  29. package/dist/cjs/src/data/models/lineup-preset.js +53 -0
  30. package/dist/cjs/src/index.d.ts +1 -0
  31. package/dist/cjs/src/index.js +6 -0
  32. package/dist/cjs/src/service/competition/event-draft.d.ts +40 -0
  33. package/dist/cjs/src/service/competition/event-draft.js +68 -0
  34. package/dist/cjs/src/service/competition/event-draft.test.d.ts +1 -0
  35. package/dist/cjs/src/service/competition/event-draft.test.js +118 -0
  36. package/dist/cjs/src/service/competition/event-rewards.d.ts +26 -0
  37. package/dist/cjs/src/service/competition/event-rewards.js +49 -0
  38. package/dist/cjs/src/service/competition/event-rewards.test.d.ts +1 -0
  39. package/dist/cjs/src/service/competition/event-rewards.test.js +58 -0
  40. package/dist/cjs/src/service/competition/event-schedule.d.ts +126 -0
  41. package/dist/cjs/src/service/competition/event-schedule.js +141 -0
  42. package/dist/cjs/src/service/competition/event-schedule.test.d.ts +1 -0
  43. package/dist/cjs/src/service/competition/event-schedule.test.js +138 -0
  44. package/dist/cjs/src/service/competition/index.d.ts +3 -0
  45. package/dist/cjs/src/service/competition/index.js +3 -0
  46. package/dist/cjs/src/service/match/index.d.ts +1 -0
  47. package/dist/cjs/src/service/match/index.js +1 -0
  48. package/dist/cjs/src/service/match/match-generator.js +25 -48
  49. package/dist/cjs/src/service/match/match-generator.test.d.ts +1 -0
  50. package/dist/cjs/src/service/match/match-generator.test.js +48 -0
  51. package/dist/cjs/src/service/match/round-robin.d.ts +11 -0
  52. package/dist/cjs/src/service/match/round-robin.js +44 -0
  53. package/dist/cjs/src/service/match/round-robin.test.d.ts +1 -0
  54. package/dist/cjs/src/service/match/round-robin.test.js +71 -0
  55. package/dist/cjs/src/service/notification/catalog/en.json +21 -0
  56. package/dist/cjs/src/service/notification/catalog/es.json +21 -0
  57. package/dist/cjs/src/service/notification/catalog/fr.json +21 -0
  58. package/dist/cjs/src/service/notification/catalog/it.json +21 -0
  59. package/dist/cjs/src/service/notification/catalog/pl.json +21 -0
  60. package/dist/cjs/src/service/notification/catalog/pt-BR.json +21 -0
  61. package/dist/cjs/src/service/notification/catalog/tr.json +21 -0
  62. package/dist/cjs/src/service/notification/notification-type.d.ts +1 -1
  63. package/dist/cjs/src/service/notification/notification-type.js +6 -1
  64. package/dist/cjs/src/service/notification/notification.test.js +3 -1
  65. package/dist/cjs/src/service/notification/registry.js +52 -0
  66. package/dist/cjs/src/service/player/player-generator.d.ts +2 -1
  67. package/dist/cjs/src/service/player/player-generator.js +37 -5
  68. package/dist/esm/src/api/index.d.ts +2 -1
  69. package/dist/esm/src/api/index.js +1 -0
  70. package/dist/esm/src/api/tactics-player-refs.d.ts +13 -0
  71. package/dist/esm/src/api/tactics-player-refs.js +40 -0
  72. package/dist/esm/src/data/event-purge.d.ts +14 -0
  73. package/dist/esm/src/data/event-purge.js +110 -0
  74. package/dist/esm/src/data/index.d.ts +1 -0
  75. package/dist/esm/src/data/index.js +1 -0
  76. package/dist/esm/src/data/init-models.js +42 -1
  77. package/dist/esm/src/data/models/competition.d.ts +1 -1
  78. package/dist/esm/src/data/models/competition.js +1 -1
  79. package/dist/esm/src/data/models/event-bot-team.d.ts +21 -0
  80. package/dist/esm/src/data/models/event-bot-team.js +40 -0
  81. package/dist/esm/src/data/models/event-competition.d.ts +29 -0
  82. package/dist/esm/src/data/models/event-competition.js +48 -0
  83. package/dist/esm/src/data/models/event-pack-player.d.ts +22 -0
  84. package/dist/esm/src/data/models/event-pack-player.js +27 -0
  85. package/dist/esm/src/data/models/event-pack.d.ts +37 -0
  86. package/dist/esm/src/data/models/event-pack.js +51 -0
  87. package/dist/esm/src/data/models/event-result.d.ts +34 -0
  88. package/dist/esm/src/data/models/event-result.js +44 -0
  89. package/dist/esm/src/data/models/event-squad.d.ts +30 -0
  90. package/dist/esm/src/data/models/event-squad.js +42 -0
  91. package/dist/esm/src/data/models/event.d.ts +51 -0
  92. package/dist/esm/src/data/models/event.js +78 -0
  93. package/dist/esm/src/data/models/index.d.ts +7 -0
  94. package/dist/esm/src/data/models/index.js +7 -0
  95. package/dist/esm/src/data/models/lineup-preset.d.ts +3 -1
  96. package/dist/esm/src/data/models/lineup-preset.js +20 -0
  97. package/dist/esm/src/index.d.ts +1 -0
  98. package/dist/esm/src/index.js +4 -0
  99. package/dist/esm/src/service/competition/event-draft.d.ts +40 -0
  100. package/dist/esm/src/service/competition/event-draft.js +62 -0
  101. package/dist/esm/src/service/competition/event-draft.test.d.ts +1 -0
  102. package/dist/esm/src/service/competition/event-draft.test.js +116 -0
  103. package/dist/esm/src/service/competition/event-rewards.d.ts +26 -0
  104. package/dist/esm/src/service/competition/event-rewards.js +44 -0
  105. package/dist/esm/src/service/competition/event-rewards.test.d.ts +1 -0
  106. package/dist/esm/src/service/competition/event-rewards.test.js +56 -0
  107. package/dist/esm/src/service/competition/event-schedule.d.ts +126 -0
  108. package/dist/esm/src/service/competition/event-schedule.js +129 -0
  109. package/dist/esm/src/service/competition/event-schedule.test.d.ts +1 -0
  110. package/dist/esm/src/service/competition/event-schedule.test.js +136 -0
  111. package/dist/esm/src/service/competition/index.d.ts +3 -0
  112. package/dist/esm/src/service/competition/index.js +3 -0
  113. package/dist/esm/src/service/match/index.d.ts +1 -0
  114. package/dist/esm/src/service/match/index.js +1 -0
  115. package/dist/esm/src/service/match/match-generator.js +25 -48
  116. package/dist/esm/src/service/match/match-generator.test.d.ts +1 -0
  117. package/dist/esm/src/service/match/match-generator.test.js +43 -0
  118. package/dist/esm/src/service/match/round-robin.d.ts +11 -0
  119. package/dist/esm/src/service/match/round-robin.js +41 -0
  120. package/dist/esm/src/service/match/round-robin.test.d.ts +1 -0
  121. package/dist/esm/src/service/match/round-robin.test.js +69 -0
  122. package/dist/esm/src/service/notification/catalog/en.json +21 -0
  123. package/dist/esm/src/service/notification/catalog/es.json +21 -0
  124. package/dist/esm/src/service/notification/catalog/fr.json +21 -0
  125. package/dist/esm/src/service/notification/catalog/it.json +21 -0
  126. package/dist/esm/src/service/notification/catalog/pl.json +21 -0
  127. package/dist/esm/src/service/notification/catalog/pt-BR.json +21 -0
  128. package/dist/esm/src/service/notification/catalog/tr.json +21 -0
  129. package/dist/esm/src/service/notification/notification-type.d.ts +1 -1
  130. package/dist/esm/src/service/notification/notification-type.js +6 -1
  131. package/dist/esm/src/service/notification/notification.test.js +3 -1
  132. package/dist/esm/src/service/notification/registry.js +52 -0
  133. package/dist/esm/src/service/player/player-generator.d.ts +2 -1
  134. package/dist/esm/src/service/player/player-generator.js +36 -4
  135. package/package.json +6 -1
@@ -1,4 +1,4 @@
1
- export declare const NOTIFICATION_TYPES: readonly ["MATCH_RESULT", "INJURY", "INJURY_RECOVERED", "SEASON_END", "GRANT_RECEIVED", "PROMOTION", "DAILY_LOGIN", "WELCOME", "COMPENSATION"];
1
+ export declare const NOTIFICATION_TYPES: readonly ["MATCH_RESULT", "INJURY", "INJURY_RECOVERED", "SEASON_END", "GRANT_RECEIVED", "PROMOTION", "DAILY_LOGIN", "WELCOME", "COMPENSATION", "EVENT_REGISTRATION_OPEN", "EVENT_REGISTRATION_CLOSING", "EVENT_STARTED", "EVENT_FINAL_ROUND", "EVENT_FINISHED"];
2
2
  export type NotificationType = typeof NOTIFICATION_TYPES[number];
3
3
  export declare const NOTIFICATION_LOCALES: readonly ["en", "es", "fr", "it", "pl", "pt-BR", "tr"];
4
4
  export type NotificationLocale = typeof NOTIFICATION_LOCALES[number];
@@ -16,7 +16,12 @@ exports.NOTIFICATION_TYPES = [
16
16
  'PROMOTION',
17
17
  'DAILY_LOGIN',
18
18
  'WELCOME',
19
- 'COMPENSATION'
19
+ 'COMPENSATION',
20
+ 'EVENT_REGISTRATION_OPEN',
21
+ 'EVENT_REGISTRATION_CLOSING',
22
+ 'EVENT_STARTED',
23
+ 'EVENT_FINAL_ROUND',
24
+ 'EVENT_FINISHED'
20
25
  ];
21
26
  // The 7 supported locales. Must stay in sync with the API VALID_LOCALES set and the UI src/locales/*.json.
22
27
  exports.NOTIFICATION_LOCALES = ['en', 'es', 'fr', 'it', 'pl', 'pt-BR', 'tr'];
@@ -132,7 +132,9 @@ function flattenStrings(node, prefix = '') {
132
132
  .toEqual(notification_type_1.NOTIFICATION_TYPES.filter((t) => registry_1.NOTIFICATION_REGISTRY[t].emitsPush).sort((a, b) => a.localeCompare(b)));
133
133
  (0, globals_1.expect)(registry_1.PUSH_NOTIFICATION_TYPES).not.toContain('COMPENSATION');
134
134
  (0, globals_1.expect)(registry_1.PUSH_NOTIFICATION_TYPES).toContain('MATCH_RESULT');
135
- (0, globals_1.expect)(registry_1.PUSH_NOTIFICATION_TYPES).toHaveLength(8);
135
+ // COMPENSATION is the ONLY in-app-only type, so every other type pushes. Expressed as a relationship
136
+ // rather than a count, so adding a type does not need this number edited.
137
+ (0, globals_1.expect)(registry_1.PUSH_NOTIFICATION_TYPES).toHaveLength(notification_type_1.NOTIFICATION_TYPES.length - 1);
136
138
  });
137
139
  (0, globals_1.it)('is opt-out: absent / null / empty / true all enable, only explicit false suppresses', () => {
138
140
  (0, globals_1.expect)((0, notification_type_1.pushEnabledFor)(undefined, 'MATCH_RESULT')).toBe(true);
@@ -88,6 +88,58 @@ exports.NOTIFICATION_REGISTRY = {
88
88
  inAppMessageKey: 'promotion.body',
89
89
  catalogKeys: ['promotion.title', 'promotion.body']
90
90
  },
91
+ // In-game events. All five deep-link to the Events section, which is where every phase of an event lives.
92
+ // eventName is the event's key; the UI resolves it to the translated name, so the catalog copy interpolates
93
+ // a key rather than a localised string the sender would have to translate.
94
+ EVENT_REGISTRATION_OPEN: {
95
+ emitsPush: true,
96
+ deepLink: () => '/events',
97
+ pushTitleKey: 'eventRegistrationOpen.title',
98
+ pushBodyKey: 'eventRegistrationOpen.body',
99
+ inAppMessageKey: 'eventRegistrationOpen.body',
100
+ catalogKeys: ['eventRegistrationOpen.title', 'eventRegistrationOpen.body']
101
+ },
102
+ EVENT_REGISTRATION_CLOSING: {
103
+ emitsPush: true,
104
+ deepLink: () => '/events',
105
+ pushTitleKey: 'eventRegistrationClosing.title',
106
+ pushBodyKey: 'eventRegistrationClosing.body',
107
+ inAppMessageKey: 'eventRegistrationClosing.body',
108
+ catalogKeys: ['eventRegistrationClosing.title', 'eventRegistrationClosing.body']
109
+ },
110
+ EVENT_STARTED: {
111
+ emitsPush: true,
112
+ deepLink: () => '/events',
113
+ pushTitleKey: 'eventStarted.title',
114
+ pushBodyKey: 'eventStarted.body',
115
+ inAppMessageKey: 'eventStarted.body',
116
+ catalogKeys: ['eventStarted.title', 'eventStarted.body']
117
+ },
118
+ EVENT_FINAL_ROUND: {
119
+ emitsPush: true,
120
+ deepLink: () => '/events',
121
+ pushTitleKey: 'eventFinalRound.title',
122
+ pushBodyKey: 'eventFinalRound.body',
123
+ inAppMessageKey: 'eventFinalRound.body',
124
+ catalogKeys: ['eventFinalRound.title', 'eventFinalRound.body']
125
+ },
126
+ EVENT_FINISHED: {
127
+ emitsPush: true,
128
+ deepLink: () => '/events',
129
+ pushTitleKey: 'eventFinished.title',
130
+ // Copy depends on whether we know where they finished; a missing position never renders "You finished th".
131
+ pushBodyKey: (p) => readString(p.position) != null || typeof p.position === 'number'
132
+ ? 'eventFinished.placed'
133
+ : 'eventFinished.body',
134
+ inAppMessageKey: (p) => readString(p.position) != null || typeof p.position === 'number'
135
+ ? 'eventFinished.placed'
136
+ : 'eventFinished.body',
137
+ params: (p) => ({
138
+ position: typeof p.position === 'number' ? String(p.position) : (readString(p.position) ?? ''),
139
+ entrants: typeof p.entrants === 'number' ? String(p.entrants) : (readString(p.entrants) ?? '')
140
+ }),
141
+ catalogKeys: ['eventFinished.title', 'eventFinished.body', 'eventFinished.placed']
142
+ },
91
143
  DAILY_LOGIN: {
92
144
  emitsPush: true,
93
145
  // Fires when the reward becomes claimable (00:00 UTC), so it opens the daily-login claim view.
@@ -35,13 +35,14 @@ export interface GachaPullConfig {
35
35
  }
36
36
  export declare const NORMAL_PULL_CONFIG: GachaPullConfig;
37
37
  export declare const QUALIFIER_PULL_CONFIG: GachaPullConfig;
38
+ export declare const EVENT_PULL_CONFIG: GachaPullConfig;
38
39
  export declare const TOURNAMENT_PULL_CONFIG: GachaPullConfig;
39
40
  export declare function scoutPullConfig(level: string): GachaPullConfig;
40
41
  export declare function rollRarity(pity: PityState, config?: GachaPullConfig): RollResult;
41
42
  export declare class PlayerGenerator {
42
43
  private constructor();
43
44
  private static generatePerformance;
44
- static generatePlayer(country: Country, _rarity?: Rarity, role?: Role, maxTraits?: boolean, birthIteration?: number): Player;
45
+ static generatePlayer(country: Country, _rarity?: Rarity, role?: Role, maxTraits?: boolean, birthIteration?: number, topSkew?: number): Player;
45
46
  static generatePlayers(count: number, countries: Country[]): Player[];
46
47
  }
47
48
  export declare function pickPlayerCountry(teamCountry: Country, allCountries: Country[]): Country;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PlayerGenerator = exports.TOURNAMENT_PULL_CONFIG = exports.QUALIFIER_PULL_CONFIG = exports.NORMAL_PULL_CONFIG = exports.PITY_STEP = exports.PITY_THRESHOLDS = void 0;
3
+ exports.PlayerGenerator = exports.TOURNAMENT_PULL_CONFIG = exports.EVENT_PULL_CONFIG = exports.QUALIFIER_PULL_CONFIG = exports.NORMAL_PULL_CONFIG = exports.PITY_STEP = exports.PITY_THRESHOLDS = void 0;
4
4
  exports.scoutPullConfig = scoutPullConfig;
5
5
  exports.rollRarity = rollRarity;
6
6
  exports.pickPlayerCountry = pickPlayerCountry;
@@ -69,6 +69,21 @@ exports.QUALIFIER_PULL_CONFIG = {
69
69
  accumulateMythicPity: true,
70
70
  accumulateSpecialPity: true
71
71
  };
72
+ // Event Pack (x5): the NATIONAL pool and structure, with TRIPLE the legendary and mythic chance. Special is
73
+ // untouched and rare absorbs the difference, so it still sums to 1. Builds all three pities, like NATIONAL.
74
+ // Owner-defined 2026-07-23.
75
+ exports.EVENT_PULL_CONFIG = {
76
+ special: 0.003,
77
+ mythic: 0.03,
78
+ legendary: 0.30,
79
+ rare: 0.667,
80
+ applyLegendaryPity: true,
81
+ applyMythicPity: true,
82
+ applySpecialPity: true,
83
+ accumulateLegendaryPity: true,
84
+ accumulateMythicPity: true,
85
+ accumulateSpecialPity: true
86
+ };
72
87
  // Tournament gacha (x5 per match): LEGENDARY–SPECIAL, no legendary pity (floor is legendary)
73
88
  exports.TOURNAMENT_PULL_CONFIG = {
74
89
  special: 0.01,
@@ -194,7 +209,7 @@ function rollRarity(pity, config = WORLD_PULL_CONFIG) {
194
209
  /* eslint-disable @typescript-eslint/no-extraneous-class */
195
210
  class PlayerGenerator {
196
211
  constructor() { }
197
- static generatePerformance(rarity, role) {
212
+ static generatePerformance(rarity, role, topSkew = 0) {
198
213
  const ATTACK = (0, stats_1.getMultipliers)(stats_1.StatsEnum.ATTACK);
199
214
  const SET = (0, stats_1.getMultipliers)(stats_1.StatsEnum.SET);
200
215
  const RECEIVE = (0, stats_1.getMultipliers)(stats_1.StatsEnum.RECEIVE);
@@ -205,8 +220,10 @@ class PlayerGenerator {
205
220
  // Stamina always uses its own per-rarity range (guaranteed floor), regardless of role weight.
206
221
  if (key === 'stamina')
207
222
  return [key, rollStamina(rarity)];
223
+ // On-role stats drive the role score, so a positive topSkew bends only these toward the top of the
224
+ // range; off-role stats stay uniform. topSkew defaults to 0 (the historic uniform draw).
208
225
  if (weights[key] > 0)
209
- return [key, (0, node_crypto_1.randomInt)(minValue, maxValue + 1)];
226
+ return [key, randomIntSkewed(minValue, maxValue, topSkew)];
210
227
  else
211
228
  return [key, (0, node_crypto_1.randomInt)(rarity_1.RarityRanges.COMMON[0], Math.max(minValue - 1, rarity_1.RarityRanges.COMMON[0] + 1))];
212
229
  })));
@@ -300,10 +317,10 @@ class PlayerGenerator {
300
317
  }
301
318
  }
302
319
  }
303
- static generatePlayer(country, _rarity, role, maxTraits, birthIteration = 1) {
320
+ static generatePlayer(country, _rarity, role, maxTraits, birthIteration = 1, topSkew = 0) {
304
321
  const rarity = _rarity ?? rollRarity({ legendaryPity: 0, mythicPity: 0, specialPity: 0 }).rarity;
305
322
  const name = (0, utils_1.generatePlayerName)(country.locales, 1)[0];
306
- const stats = PlayerGenerator.generatePerformance(rarity, role);
323
+ const stats = PlayerGenerator.generatePerformance(rarity, role, topSkew);
307
324
  const roles = (0, role_1.assignRoles)(stats, rarity, role);
308
325
  const traits = (0, trait_1.assignTraits)(roles, rarity, maxTraits);
309
326
  const birthAge = (0, node_crypto_1.randomInt)(15, 21);
@@ -345,6 +362,21 @@ function rollStamina(rarity) {
345
362
  const [low, high] = rarity_1.StaminaRanges[rarity];
346
363
  return (0, node_crypto_1.randomInt)(low, high + 1);
347
364
  }
365
+ /**
366
+ * A crypto.randomInt draw over [min, max] inclusive, optionally skewed toward the top.
367
+ *
368
+ * skew = 0 is the plain uniform draw the generator has always used. A positive skew bends a uniform fraction
369
+ * upward with u^(1/(1+skew)), so higher values become progressively likelier while the maximum is never forced,
370
+ * which keeps the result random rather than a maxed stat. Event drafts use this to make the lower rarities'
371
+ * recruits cluster near the top of their range (see EVENT_DRAFT_TOP_SKEW).
372
+ */
373
+ function randomIntSkewed(min, max, skew) {
374
+ if (skew <= 0 || max <= min)
375
+ return (0, node_crypto_1.randomInt)(min, max + 1);
376
+ const u = (0, node_crypto_1.randomInt)(0, 1000001) / 1000000;
377
+ const frac = Math.pow(u, 1 / (1 + skew));
378
+ return min + Math.round(frac * (max - min));
379
+ }
348
380
  function assignValues(stat, low, high, performance) {
349
381
  performance_stats_1.performanceStatKeys.filter((key) => stat[key] != null && stat[key] > 0)
350
382
  .forEach((key) => {
@@ -1,4 +1,5 @@
1
1
  import { BoxScore, DataProps, DeclineStatus, Division as _Division, League as _League, Match as _Match, MatchSet as _MatchSet, Player as _Player, PlayerPosition, Qualifier as _Qualifier, QualifierMatch as _QualifierMatch, Rally as _Rally, RallyEvent as _RallyEvent, PointBreakdown, RallyMetrics, Season as _Season, Standing as _Standing, Team as _Team, Tournament as _Tournament, TournamentMatch as _TournamentMatch, National as _National, NationalMatch as _NationalMatch, Country as _Country, BlockingAssignment, CourtPosition, ConditionLogic, EnergyBand, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand } from '../service';
2
+ export * from './tactics-player-refs';
2
3
  export type Rally = DataProps<_Rally> & {
3
4
  homePlayerPosition: PlayerPosition[];
4
5
  awayPlayerPosition: PlayerPosition[];
@@ -16,7 +17,7 @@ export interface Results {
16
17
  away: number[];
17
18
  }
18
19
  export interface MatchCompetition {
19
- type: 'LEAGUE' | 'NATIONAL' | 'QUALIFIER' | 'TOURNAMENT';
20
+ type: 'LEAGUE' | 'NATIONAL' | 'QUALIFIER' | 'TOURNAMENT' | 'EVENT';
20
21
  countryName?: string;
21
22
  countryAlpha2?: string;
22
23
  countryAlpha3?: string;
@@ -1 +1,2 @@
1
1
  import { CourtPosition } from '../service';
2
+ export * from './tactics-player-refs';
@@ -0,0 +1,13 @@
1
+ import type { Tactics } from '.';
2
+ /**
3
+ * Every player id a tactics config references, anywhere: the six court slots and the libero, the bench, libero
4
+ * replacements (flat and per-set), the second libero, designated subs and their per-set overrides, designated
5
+ * setters (flat and per-set), the keys of the offensive-weight and blocking-target maps, injury replacements
6
+ * and the libero-injury reserves.
7
+ *
8
+ * There is exactly ONE implementation of this on purpose. It backs three rules that must agree or a player
9
+ * slips through: the dismiss/retire guard (a referenced player cannot be removed), the event rarity check
10
+ * (every referenced player must match the event's rarity), and the simulator's re-validation of an event entry
11
+ * at the registration deadline. A field missed here silently unlocks a player in all three.
12
+ */
13
+ export declare function collectTacticsPlayerIds(config: Tactics): string[];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Every player id a tactics config references, anywhere: the six court slots and the libero, the bench, libero
3
+ * replacements (flat and per-set), the second libero, designated subs and their per-set overrides, designated
4
+ * setters (flat and per-set), the keys of the offensive-weight and blocking-target maps, injury replacements
5
+ * and the libero-injury reserves.
6
+ *
7
+ * There is exactly ONE implementation of this on purpose. It backs three rules that must agree or a player
8
+ * slips through: the dismiss/retire guard (a referenced player cannot be removed), the event rarity check
9
+ * (every referenced player must match the event's rarity), and the simulator's re-validation of an event entry
10
+ * at the registration deadline. A field missed here silently unlocks a player in all three.
11
+ */
12
+ export function collectTacticsPlayerIds(config) {
13
+ const lineup = config.lineup;
14
+ const referenced = [
15
+ lineup[0], lineup[1], lineup[2], lineup[3], lineup[4], lineup[5], lineup[6],
16
+ ...(Array.isArray(lineup.bench) ? lineup.bench : []),
17
+ ...(config.liberoReplacements ?? []),
18
+ ...((config.designatedSubs ?? []).flatMap(ds => [
19
+ ds.starterId,
20
+ ds.benchId,
21
+ ...((ds.sets ?? []).map(s => s.benchId))
22
+ ])),
23
+ config.secondLibero,
24
+ ...((config.liberoReplacementSets ?? []).flat()),
25
+ ...(config.designatedSetters ?? []),
26
+ ...((config.offensivePreferences ?? []).flatMap(p => Object.keys(p.weights))),
27
+ ...((config.systemSets ?? []).flatMap(s => s.designatedSetters)),
28
+ ...((config.offensivePreferenceSets ?? []).flat().flatMap(p => Object.keys(p.weights))),
29
+ ...((config.blockingPreferences ?? []).flatMap(p => Object.keys(p.targets))),
30
+ ...((config.blockingPreferenceSets ?? []).flat().flatMap(p => Object.keys(p.targets))),
31
+ // Injury replacements reference reserves (roster players outside the lineup); roster membership is the only
32
+ // player check possible here (the schema enforces the bench/reserve role rules).
33
+ ...((config.injuryReplacements ?? []).flatMap(ir => [ir.starterId, ir.replacementId, ir.benchBackfillId])),
34
+ // Libero-injury ids reference reserves/bench players; roster membership is the only check possible here.
35
+ ...(config.liberoInjury != null
36
+ ? [config.liberoInjury.preMatchReplacementId, config.liberoInjury.secondLiberoBackfillId, config.liberoInjury.inMatchDesignateId]
37
+ : [])
38
+ ];
39
+ return [...new Set(referenced.filter((id) => typeof id === 'string' && id.length > 0))];
40
+ }
@@ -0,0 +1,14 @@
1
+ import { Sequelize } from 'sequelize';
2
+ /**
3
+ * Delete everything an event produced, keeping only the Event row itself (with purged_at stamped) so the game
4
+ * retains a record that it ran. Pass `removeEvent` to drop that row too, which is what an admin delete does.
5
+ *
6
+ * Order matters: MatchResult has no FK to Match and must go explicitly (same as the weekly friendly purge);
7
+ * deleting the Match then cascades MatchSet -> Rally/BoxScore, VPER, MatchRating, PlayerImprovementLog and
8
+ * MatchPreset. The drafted squads go the same way as the bots: their throwaway teams and every player in them
9
+ * are deleted, along with any packs whose choices were never picked.
10
+ *
11
+ * EventResult is deliberately NOT touched. It is the permanent record of where each entrant finished and the
12
+ * only event row besides Event itself that outlives this.
13
+ */
14
+ export declare function purgeEvent(sequelize: Sequelize, eventId: string, removeEvent?: boolean): Promise<void>;
@@ -0,0 +1,110 @@
1
+ import { QueryTypes } from 'sequelize';
2
+ import { CompetitionMatchModel, CompetitionModel, CompetitionStandingsModel, CompetitionTeamsModel, EventBotTeamModel, EventCompetitionModel, EventPackModel, EventPackPlayerModel, EventSquadModel, EventModel, LineupPresetModel, MatchModel, MatchResultModel, PlayerModel, PlayerTeamModel, TeamModel } from './models';
3
+ import logger from '../logger';
4
+ // Tearing an event down. This lives in Types rather than in either service because BOTH perform it and they
5
+ // must agree exactly: the simulator purges an event on its scheduled date, and the admin API purges one that
6
+ // is being deleted outright. A second implementation of a destructive delete is how orphans get left behind.
7
+ /**
8
+ * Delete everything an event produced, keeping only the Event row itself (with purged_at stamped) so the game
9
+ * retains a record that it ran. Pass `removeEvent` to drop that row too, which is what an admin delete does.
10
+ *
11
+ * Order matters: MatchResult has no FK to Match and must go explicitly (same as the weekly friendly purge);
12
+ * deleting the Match then cascades MatchSet -> Rally/BoxScore, VPER, MatchRating, PlayerImprovementLog and
13
+ * MatchPreset. The drafted squads go the same way as the bots: their throwaway teams and every player in them
14
+ * are deleted, along with any packs whose choices were never picked.
15
+ *
16
+ * EventResult is deliberately NOT touched. It is the permanent record of where each entrant finished and the
17
+ * only event row besides Event itself that outlives this.
18
+ */
19
+ export async function purgeEvent(sequelize, eventId, removeEvent = false) {
20
+ await sequelize.transaction(async (transaction) => {
21
+ const competitions = await EventCompetitionModel.findAll({
22
+ where: { event_id: eventId },
23
+ attributes: ['competition_id'],
24
+ transaction
25
+ });
26
+ const competitionIds = competitions.map(c => c.competition_id);
27
+ const botTeams = await EventBotTeamModel.findAll({
28
+ where: { event_id: eventId },
29
+ attributes: ['team_id'],
30
+ transaction
31
+ });
32
+ const squads = await EventSquadModel.findAll({
33
+ where: { event_id: eventId },
34
+ attributes: ['team_id'],
35
+ transaction
36
+ });
37
+ // Bots and drafted squads are the same thing here: throwaway teams full of generated players.
38
+ const botTeamIds = [...botTeams.map(b => b.team_id), ...squads.map(s => s.team_id)];
39
+ if (competitionIds.length > 0) {
40
+ const matchRows = await CompetitionMatchModel.findAll({
41
+ where: { competition_id: competitionIds },
42
+ attributes: ['match_id'],
43
+ transaction
44
+ });
45
+ const matchIds = matchRows.map(m => m.match_id);
46
+ if (matchIds.length > 0) {
47
+ await MatchResultModel.destroy({ where: { match_id: matchIds }, transaction });
48
+ await CompetitionMatchModel.destroy({ where: { competition_id: competitionIds }, transaction });
49
+ await MatchModel.destroy({ where: { match_id: matchIds }, transaction });
50
+ }
51
+ await CompetitionStandingsModel.destroy({ where: { competition_id: competitionIds }, transaction });
52
+ await sequelize.query('DELETE FROM "CompetitionStandingsMatch" WHERE competition_id IN (:ids)', {
53
+ replacements: { ids: competitionIds }, type: QueryTypes.DELETE, transaction
54
+ });
55
+ await CompetitionTeamsModel.destroy({ where: { competition_id: competitionIds }, transaction });
56
+ await EventCompetitionModel.destroy({ where: { event_id: eventId }, transaction });
57
+ await CompetitionModel.destroy({ where: { competition_id: competitionIds }, transaction });
58
+ }
59
+ // Packs first: an unpicked choice still points at a Player row, and the pack points at the picked one, so
60
+ // both references have to go before the players themselves.
61
+ const packs = await EventPackModel.findAll({
62
+ where: { event_id: eventId },
63
+ attributes: ['pack_id'],
64
+ transaction
65
+ });
66
+ if (packs.length > 0) {
67
+ const packIds = packs.map(p => p.pack_id);
68
+ const choices = await EventPackPlayerModel.findAll({
69
+ where: { pack_id: packIds },
70
+ attributes: ['player_id'],
71
+ transaction
72
+ });
73
+ await EventPackPlayerModel.destroy({ where: { pack_id: packIds }, transaction });
74
+ await EventPackModel.destroy({ where: { event_id: eventId }, transaction });
75
+ // Choices that were never picked belong to no team, so nothing below would reach them.
76
+ const looseIds = choices.map(c => c.player_id);
77
+ if (looseIds.length > 0) {
78
+ await PlayerTeamModel.destroy({ where: { player_id: looseIds }, transaction });
79
+ await PlayerModel.destroy({ where: { player_id: looseIds }, transaction });
80
+ }
81
+ }
82
+ // Squads and event lineups go before the teams so no FK from a preset can block the team delete.
83
+ await EventSquadModel.destroy({ where: { event_id: eventId }, transaction });
84
+ await LineupPresetModel.destroy({ where: { event_id: eventId }, transaction });
85
+ if (botTeamIds.length > 0) {
86
+ const botPlayers = await PlayerTeamModel.findAll({
87
+ where: { team_id: botTeamIds },
88
+ attributes: ['player_id'],
89
+ transaction
90
+ });
91
+ const botPlayerIds = botPlayers.map(p => p.player_id);
92
+ await EventBotTeamModel.destroy({ where: { event_id: eventId }, transaction });
93
+ await PlayerTeamModel.destroy({ where: { team_id: botTeamIds }, transaction });
94
+ if (botPlayerIds.length > 0) {
95
+ await PlayerModel.destroy({ where: { player_id: botPlayerIds }, transaction });
96
+ }
97
+ await sequelize.query('DELETE FROM "Tactics" WHERE team_id IN (:ids)', {
98
+ replacements: { ids: botTeamIds }, type: QueryTypes.DELETE, transaction
99
+ });
100
+ await TeamModel.destroy({ where: { team_id: botTeamIds }, transaction });
101
+ }
102
+ if (removeEvent) {
103
+ await EventModel.destroy({ where: { event_id: eventId }, transaction });
104
+ }
105
+ else {
106
+ await EventModel.update({ status: 'ARCHIVED', purged_at: new Date() }, { where: { event_id: eventId }, transaction });
107
+ }
108
+ });
109
+ logger.info(`${removeEvent ? 'Deleted' : 'Purged'} event ${eventId}`);
110
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './models';
2
2
  export * from './transformers';
3
3
  export * from './common';
4
+ export * from './event-purge';
4
5
  export { initModels } from './init-models';
@@ -1,4 +1,5 @@
1
1
  export * from './models';
2
2
  export * from './transformers';
3
3
  export * from './common';
4
+ export * from './event-purge';
4
5
  export { initModels } from './init-models';
@@ -1,4 +1,4 @@
1
- import { AuthIdentityModel, AuthSessionModel, AuthUserModel, BoxScoreModel, CoachModel, ScoutModel, PhysicianModel, PhysiotherapistModel, BoxScoreTotalsModel, CompetitionChampionModel, CurrencyTransactionModel, GachaPityModel, GachaPullHistoryModel, WishlistModel, UserSettingsModel, UserDeviceTokenModel, CompetitionMatchModel, CompetitionMVPModel, CompetitionModel, CompetitionStandingsMatchModel, CompetitionStandingsModel, CompetitionTeamsModel, CountryModel, DivisionModel, DivisionSeasonModel, IterationModel, LeagueModel, MatchModel, MatchRatingModel, MatchResultModel, MatchSetModel, PerformanceStatsModel, PlayerModel, PlayerImprovementLogModel, PlayerDeclineLogModel, PlayerTeamModel, RetiredPlayerModel, PromotionMatchModel, FriendlyModel, RegionQualifierModel, NationalCountryModel, RallyModel, TacticsModel, LineupPresetModel, MatchPresetModel, TeamModel, TeamReplacementModel, UserCurrencyModel, UserPlayerProgressModel, UserPullGrantModel, UserTeamModel, VPERModel, NotificationModel, AppConfigModel, LegacyTeamFlagModel } from './models';
1
+ import { AuthIdentityModel, AuthSessionModel, AuthUserModel, BoxScoreModel, CoachModel, ScoutModel, PhysicianModel, PhysiotherapistModel, BoxScoreTotalsModel, CompetitionChampionModel, CurrencyTransactionModel, GachaPityModel, GachaPullHistoryModel, WishlistModel, UserSettingsModel, UserDeviceTokenModel, CompetitionMatchModel, CompetitionMVPModel, CompetitionModel, CompetitionStandingsMatchModel, CompetitionStandingsModel, CompetitionTeamsModel, CountryModel, DivisionModel, DivisionSeasonModel, IterationModel, LeagueModel, MatchModel, MatchRatingModel, MatchResultModel, MatchSetModel, PerformanceStatsModel, PlayerModel, PlayerImprovementLogModel, PlayerDeclineLogModel, PlayerTeamModel, RetiredPlayerModel, PromotionMatchModel, FriendlyModel, RegionQualifierModel, NationalCountryModel, RallyModel, TacticsModel, LineupPresetModel, MatchPresetModel, EventModel, EventCompetitionModel, EventSquadModel, EventPackModel, EventPackPlayerModel, EventResultModel, EventBotTeamModel, TeamModel, TeamReplacementModel, UserCurrencyModel, UserPlayerProgressModel, UserPullGrantModel, UserTeamModel, VPERModel, NotificationModel, AppConfigModel, LegacyTeamFlagModel } from './models';
2
2
  export function initModels(sequelize) {
3
3
  const Coach = CoachModel.initModel(sequelize);
4
4
  const Scout = ScoutModel.initModel(sequelize);
@@ -8,6 +8,13 @@ export function initModels(sequelize) {
8
8
  const Tactics = TacticsModel.initModel(sequelize);
9
9
  const LineupPreset = LineupPresetModel.initModel(sequelize);
10
10
  const MatchPreset = MatchPresetModel.initModel(sequelize);
11
+ const Event = EventModel.initModel(sequelize);
12
+ const EventCompetition = EventCompetitionModel.initModel(sequelize);
13
+ const EventSquad = EventSquadModel.initModel(sequelize);
14
+ const EventPack = EventPackModel.initModel(sequelize);
15
+ const EventPackPlayer = EventPackPlayerModel.initModel(sequelize);
16
+ const EventResult = EventResultModel.initModel(sequelize);
17
+ const EventBotTeam = EventBotTeamModel.initModel(sequelize);
11
18
  const AuthUser = AuthUserModel.initModel(sequelize);
12
19
  const UserCurrency = UserCurrencyModel.initModel(sequelize);
13
20
  const UserPlayerProgress = UserPlayerProgressModel.initModel(sequelize);
@@ -226,6 +233,33 @@ export function initModels(sequelize) {
226
233
  MatchPreset.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
227
234
  MatchPreset.belongsTo(LineupPreset, { as: 'preset', foreignKey: 'preset_id' });
228
235
  LineupPreset.hasMany(MatchPreset, { as: 'MatchPresets', foreignKey: 'preset_id' });
236
+ // In-game events. An event owns one Competition per group plus one for the final, the entries that registered
237
+ // for it, the event-scoped lineup presets those entries point at, and the filler bots generated for it.
238
+ // Nothing here is in a default scope: only the event endpoints and the event scheduler load these.
239
+ Event.hasMany(EventCompetition, { as: 'EventCompetitions', foreignKey: 'event_id' });
240
+ EventCompetition.belongsTo(Event, { as: 'Event', foreignKey: 'event_id' });
241
+ EventCompetition.belongsTo(Competition, { as: 'Competition', foreignKey: 'competition_id' });
242
+ Competition.hasOne(EventCompetition, { as: 'EventCompetition', foreignKey: 'competition_id' });
243
+ // The drafted squad an entrant plays with, the packs they opened to build it, and where they finished.
244
+ Event.hasMany(EventSquad, { as: 'EventSquads', foreignKey: 'event_id' });
245
+ EventSquad.belongsTo(Event, { as: 'Event', foreignKey: 'event_id' });
246
+ EventSquad.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
247
+ Event.hasMany(EventPack, { as: 'EventPacks', foreignKey: 'event_id' });
248
+ EventPack.belongsTo(Event, { as: 'Event', foreignKey: 'event_id' });
249
+ EventPack.belongsTo(Player, { as: 'picked', foreignKey: 'picked_player_id' });
250
+ EventPack.hasMany(EventPackPlayer, { as: 'choices', foreignKey: 'pack_id' });
251
+ EventPackPlayer.belongsTo(EventPack, { as: 'pack', foreignKey: 'pack_id' });
252
+ EventPackPlayer.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
253
+ // Keyed to the entrant's REAL team, and the only event row besides Event that outlives the purge.
254
+ Event.hasMany(EventResult, { as: 'EventResults', foreignKey: 'event_id' });
255
+ EventResult.belongsTo(Event, { as: 'Event', foreignKey: 'event_id' });
256
+ EventResult.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
257
+ Team.hasMany(EventResult, { as: 'EventResults', foreignKey: 'team_id' });
258
+ LineupPreset.belongsTo(Event, { as: 'Event', foreignKey: 'event_id' });
259
+ Event.hasMany(EventBotTeam, { as: 'EventBotTeams', foreignKey: 'event_id' });
260
+ EventBotTeam.belongsTo(Event, { as: 'Event', foreignKey: 'event_id' });
261
+ EventBotTeam.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
262
+ Team.hasOne(EventBotTeam, { as: 'EventBotTeam', foreignKey: 'team_id' });
229
263
  Team.belongsTo(Country, { as: 'country', foreignKey: 'country_id' });
230
264
  Team.belongsToMany(Competition, {
231
265
  as: 'Competitions',
@@ -301,6 +335,13 @@ export function initModels(sequelize) {
301
335
  Tactics,
302
336
  LineupPreset,
303
337
  MatchPreset,
338
+ Event,
339
+ EventCompetition,
340
+ EventSquad,
341
+ EventPack,
342
+ EventPackPlayer,
343
+ EventResult,
344
+ EventBotTeam,
304
345
  UserTeam,
305
346
  UserCurrency,
306
347
  UserPlayerProgress,
@@ -19,7 +19,7 @@ export interface CompetitionAttributes {
19
19
  LowerPromotionMatches?: PromotionMatchAttributes[];
20
20
  }
21
21
  export type CompetitionPk = 'competition_id';
22
- export type CompetitionType = 'LEAGUE' | 'TOURNAMENT' | 'QUALIFIER' | 'NATIONAL';
22
+ export type CompetitionType = 'LEAGUE' | 'TOURNAMENT' | 'QUALIFIER' | 'NATIONAL' | 'EVENT';
23
23
  export type CompetitionId = CompetitionModel[CompetitionPk];
24
24
  export type CompetitionCreationAttributes = CompetitionAttributes;
25
25
  export declare class CompetitionModel extends Model<CompetitionAttributes, CompetitionCreationAttributes> implements CompetitionAttributes {
@@ -18,7 +18,7 @@ export class CompetitionModel extends Model {
18
18
  }
19
19
  },
20
20
  type: {
21
- type: DataTypes.ENUM('LEAGUE', 'TOURNAMENT', 'QUALIFIER', 'NATIONAL'),
21
+ type: DataTypes.ENUM('LEAGUE', 'TOURNAMENT', 'QUALIFIER', 'NATIONAL', 'EVENT'),
22
22
  allowNull: false
23
23
  },
24
24
  status: {
@@ -0,0 +1,21 @@
1
+ import * as Sequelize from 'sequelize';
2
+ import { Model } from 'sequelize';
3
+ import { EventId, EventModel, TeamId, TeamModel } from '.';
4
+ export interface EventBotTeamAttributes {
5
+ team_id: string;
6
+ event_id: string;
7
+ }
8
+ export type EventBotTeamPk = 'team_id';
9
+ export type EventBotTeamId = EventBotTeamModel[EventBotTeamPk];
10
+ export type EventBotTeamCreationAttributes = EventBotTeamAttributes;
11
+ export declare class EventBotTeamModel extends Model<EventBotTeamAttributes, EventBotTeamCreationAttributes> implements EventBotTeamAttributes {
12
+ team_id: string;
13
+ event_id: string;
14
+ team: TeamModel;
15
+ getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
16
+ setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
17
+ Event: EventModel;
18
+ getEvent: Sequelize.BelongsToGetAssociationMixin<EventModel>;
19
+ setEvent: Sequelize.BelongsToSetAssociationMixin<EventModel, EventId>;
20
+ static initModel(sequelize: Sequelize.Sequelize): typeof EventBotTeamModel;
21
+ }
@@ -0,0 +1,40 @@
1
+ import { DataTypes, Model } from 'sequelize';
2
+ export class EventBotTeamModel extends Model {
3
+ static initModel(sequelize) {
4
+ return EventBotTeamModel.init({
5
+ team_id: {
6
+ type: DataTypes.UUID,
7
+ allowNull: false,
8
+ primaryKey: true,
9
+ references: {
10
+ model: 'Team',
11
+ key: 'team_id'
12
+ }
13
+ },
14
+ event_id: {
15
+ type: DataTypes.UUID,
16
+ allowNull: false,
17
+ references: {
18
+ model: 'Event',
19
+ key: 'event_id'
20
+ }
21
+ }
22
+ }, {
23
+ sequelize,
24
+ tableName: 'EventBotTeam',
25
+ schema: 'public',
26
+ timestamps: false,
27
+ indexes: [
28
+ {
29
+ name: 'EventBotTeam_pk',
30
+ unique: true,
31
+ fields: [{ name: 'team_id' }]
32
+ },
33
+ {
34
+ name: 'EventBotTeam_event_id_idx',
35
+ fields: [{ name: 'event_id' }]
36
+ }
37
+ ]
38
+ });
39
+ }
40
+ }
@@ -0,0 +1,29 @@
1
+ import * as Sequelize from 'sequelize';
2
+ import { Model } from 'sequelize';
3
+ import { CompetitionAttributes, CompetitionId, CompetitionModel, EventId, EventModel } from '.';
4
+ /** A group table, or the single Sunday final. */
5
+ export type EventCompetitionStage = 'GROUP' | 'FINAL';
6
+ export interface EventCompetitionAttributes {
7
+ competition_id: string;
8
+ event_id: string;
9
+ stage: EventCompetitionStage;
10
+ /** 0-based group number for a GROUP row; null for the FINAL. */
11
+ group_index: number | null;
12
+ Competition?: CompetitionAttributes;
13
+ }
14
+ export type EventCompetitionPk = 'competition_id';
15
+ export type EventCompetitionId = EventCompetitionModel[EventCompetitionPk];
16
+ export type EventCompetitionCreationAttributes = EventCompetitionAttributes;
17
+ export declare class EventCompetitionModel extends Model<EventCompetitionAttributes, EventCompetitionCreationAttributes> implements EventCompetitionAttributes {
18
+ competition_id: string;
19
+ event_id: string;
20
+ stage: EventCompetitionStage;
21
+ group_index: number | null;
22
+ Competition: CompetitionModel;
23
+ getCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
24
+ setCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
25
+ Event: EventModel;
26
+ getEvent: Sequelize.BelongsToGetAssociationMixin<EventModel>;
27
+ setEvent: Sequelize.BelongsToSetAssociationMixin<EventModel, EventId>;
28
+ static initModel(sequelize: Sequelize.Sequelize): typeof EventCompetitionModel;
29
+ }