volleyballsimtypes 0.0.519 → 0.0.520

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 (81) hide show
  1. package/dist/cjs/src/api/index.d.ts +14 -1
  2. package/dist/cjs/src/data/init-models.js +14 -0
  3. package/dist/cjs/src/data/models/auth-identity.d.ts +1 -1
  4. package/dist/cjs/src/data/models/auth-identity.js +1 -1
  5. package/dist/cjs/src/data/models/index.d.ts +3 -0
  6. package/dist/cjs/src/data/models/index.js +3 -0
  7. package/dist/cjs/src/data/models/jersey-badge.d.ts +32 -0
  8. package/dist/cjs/src/data/models/jersey-badge.js +60 -0
  9. package/dist/cjs/src/data/models/premium-grant.d.ts +39 -0
  10. package/dist/cjs/src/data/models/premium-grant.js +82 -0
  11. package/dist/cjs/src/data/models/team-jersey.d.ts +20 -0
  12. package/dist/cjs/src/data/models/team-jersey.js +38 -0
  13. package/dist/cjs/src/data/models/team.d.ts +3 -1
  14. package/dist/cjs/src/data/transformers/team.js +4 -1
  15. package/dist/cjs/src/service/index.d.ts +1 -0
  16. package/dist/cjs/src/service/index.js +1 -0
  17. package/dist/cjs/src/service/player/improvement.test.js +5 -5
  18. package/dist/cjs/src/service/player/stats.js +30 -16
  19. package/dist/cjs/src/service/player/stats.test.js +6 -6
  20. package/dist/cjs/src/service/premium/index.d.ts +3 -0
  21. package/dist/cjs/src/service/premium/index.js +19 -0
  22. package/dist/cjs/src/service/premium/patreon-tier-map.d.ts +5 -0
  23. package/dist/cjs/src/service/premium/patreon-tier-map.js +22 -0
  24. package/dist/cjs/src/service/premium/patreon-tier-map.test.d.ts +1 -0
  25. package/dist/cjs/src/service/premium/patreon-tier-map.test.js +48 -0
  26. package/dist/cjs/src/service/premium/premium-grant.d.ts +17 -0
  27. package/dist/cjs/src/service/premium/premium-grant.js +15 -0
  28. package/dist/cjs/src/service/premium/premium-tier.d.ts +10 -0
  29. package/dist/cjs/src/service/premium/premium-tier.js +24 -0
  30. package/dist/cjs/src/service/premium/premium.test.d.ts +1 -0
  31. package/dist/cjs/src/service/premium/premium.test.js +93 -0
  32. package/dist/cjs/src/service/team/index.d.ts +1 -0
  33. package/dist/cjs/src/service/team/index.js +1 -0
  34. package/dist/cjs/src/service/team/jersey.d.ts +74 -0
  35. package/dist/cjs/src/service/team/jersey.js +33 -0
  36. package/dist/cjs/src/service/team/schemas/team.z.d.ts +69 -0
  37. package/dist/cjs/src/service/team/schemas/team.z.js +3 -1
  38. package/dist/cjs/src/service/team/team.d.ts +2 -0
  39. package/dist/cjs/src/service/team/team.js +2 -1
  40. package/dist/cjs/src/stat-config/improvement-floors.json +204 -204
  41. package/dist/esm/src/api/index.d.ts +14 -1
  42. package/dist/esm/src/data/init-models.js +15 -1
  43. package/dist/esm/src/data/models/auth-identity.d.ts +1 -1
  44. package/dist/esm/src/data/models/auth-identity.js +1 -1
  45. package/dist/esm/src/data/models/index.d.ts +3 -0
  46. package/dist/esm/src/data/models/index.js +3 -0
  47. package/dist/esm/src/data/models/jersey-badge.d.ts +32 -0
  48. package/dist/esm/src/data/models/jersey-badge.js +56 -0
  49. package/dist/esm/src/data/models/premium-grant.d.ts +39 -0
  50. package/dist/esm/src/data/models/premium-grant.js +78 -0
  51. package/dist/esm/src/data/models/team-jersey.d.ts +20 -0
  52. package/dist/esm/src/data/models/team-jersey.js +34 -0
  53. package/dist/esm/src/data/models/team.d.ts +3 -1
  54. package/dist/esm/src/data/transformers/team.js +4 -1
  55. package/dist/esm/src/service/index.d.ts +1 -0
  56. package/dist/esm/src/service/index.js +1 -0
  57. package/dist/esm/src/service/player/improvement.test.js +5 -5
  58. package/dist/esm/src/service/player/stats.js +30 -16
  59. package/dist/esm/src/service/player/stats.test.js +6 -6
  60. package/dist/esm/src/service/premium/index.d.ts +3 -0
  61. package/dist/esm/src/service/premium/index.js +3 -0
  62. package/dist/esm/src/service/premium/patreon-tier-map.d.ts +5 -0
  63. package/dist/esm/src/service/premium/patreon-tier-map.js +18 -0
  64. package/dist/esm/src/service/premium/patreon-tier-map.test.d.ts +1 -0
  65. package/dist/esm/src/service/premium/patreon-tier-map.test.js +46 -0
  66. package/dist/esm/src/service/premium/premium-grant.d.ts +17 -0
  67. package/dist/esm/src/service/premium/premium-grant.js +11 -0
  68. package/dist/esm/src/service/premium/premium-tier.d.ts +10 -0
  69. package/dist/esm/src/service/premium/premium-tier.js +19 -0
  70. package/dist/esm/src/service/premium/premium.test.d.ts +1 -0
  71. package/dist/esm/src/service/premium/premium.test.js +91 -0
  72. package/dist/esm/src/service/team/index.d.ts +1 -0
  73. package/dist/esm/src/service/team/index.js +1 -0
  74. package/dist/esm/src/service/team/jersey.d.ts +74 -0
  75. package/dist/esm/src/service/team/jersey.js +30 -0
  76. package/dist/esm/src/service/team/schemas/team.z.d.ts +69 -0
  77. package/dist/esm/src/service/team/schemas/team.z.js +3 -1
  78. package/dist/esm/src/service/team/team.d.ts +2 -0
  79. package/dist/esm/src/service/team/team.js +2 -1
  80. package/dist/esm/src/stat-config/improvement-floors.json +204 -204
  81. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { BaseConfig, 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, DefenceFormation, LineupFunction, CourtPosition, ConditionLogic, EnergyBand, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand } from '../service';
1
+ import { BaseConfig, BoxScore, DataProps, DeclineStatus, JerseyConfig, 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, DefenceFormation, LineupFunction, CourtPosition, ConditionLogic, EnergyBand, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand } from '../service';
2
2
  export * from './tactics-player-refs';
3
3
  export type Rally = DataProps<_Rally> & {
4
4
  homePlayerPosition: PlayerPosition[];
@@ -60,6 +60,17 @@ export interface ApiPlayerInjuryStatus {
60
60
  severity: number;
61
61
  injuredUntil: string;
62
62
  }
63
+ export type ChampionRegion = 'azureich' | 'borealas' | 'varune' | 'tirdeas';
64
+ export type ChampionBadgeSpec = {
65
+ type: 'league';
66
+ } | {
67
+ type: 'nationals';
68
+ } | {
69
+ type: 'regionals';
70
+ region: ChampionRegion;
71
+ } | {
72
+ type: 'wcc';
73
+ };
63
74
  export type Player = Omit<DataProps<_Player>, 'stats' | 'birthAge' | 'birthIteration' | 'declineProfile' | 'nickname' | 'injuryProneProfile' | 'injury'> & {
64
75
  -readonly [K in 'birthAge' | 'birthIteration']?: DataProps<_Player>[K];
65
76
  } & {
@@ -70,6 +81,8 @@ export type Player = Omit<DataProps<_Player>, 'stats' | 'birthAge' | 'birthItera
70
81
  declineStatus?: DeclineStatus;
71
82
  injuryStatus?: ApiPlayerInjuryStatus;
72
83
  nickname?: string;
84
+ jerseyConfig?: JerseyConfig;
85
+ championBadge?: ChampionBadgeSpec;
73
86
  };
74
87
  export interface StartingLineup {
75
88
  [CourtPosition.LEFT_FRONT]: string;
@@ -9,6 +9,7 @@ function initModels(sequelize) {
9
9
  const Physiotherapist = models_1.PhysiotherapistModel.initModel(sequelize);
10
10
  const CurrencyTransaction = models_1.CurrencyTransactionModel.initModel(sequelize);
11
11
  const Tactics = models_1.TacticsModel.initModel(sequelize);
12
+ const TeamJersey = models_1.TeamJerseyModel.initModel(sequelize);
12
13
  const LineupPreset = models_1.LineupPresetModel.initModel(sequelize);
13
14
  const MatchPreset = models_1.MatchPresetModel.initModel(sequelize);
14
15
  const Event = models_1.EventModel.initModel(sequelize);
@@ -24,6 +25,7 @@ function initModels(sequelize) {
24
25
  const GachaPity = models_1.GachaPityModel.initModel(sequelize);
25
26
  const GachaPullHistory = models_1.GachaPullHistoryModel.initModel(sequelize);
26
27
  const UserPullGrant = models_1.UserPullGrantModel.initModel(sequelize);
28
+ const PremiumGrant = models_1.PremiumGrantModel.initModel(sequelize);
27
29
  const Wishlist = models_1.WishlistModel.initModel(sequelize);
28
30
  const UserSettings = models_1.UserSettingsModel.initModel(sequelize);
29
31
  const UserDeviceToken = models_1.UserDeviceTokenModel.initModel(sequelize);
@@ -39,6 +41,7 @@ function initModels(sequelize) {
39
41
  const CompetitionStandingsMatch = models_1.CompetitionStandingsMatchModel.initModel(sequelize);
40
42
  const CompetitionTeams = models_1.CompetitionTeamsModel.initModel(sequelize);
41
43
  const CompetitionChampion = models_1.CompetitionChampionModel.initModel(sequelize);
44
+ const JerseyBadge = models_1.JerseyBadgeModel.initModel(sequelize);
42
45
  const CompetitionMatch = models_1.CompetitionMatchModel.initModel(sequelize);
43
46
  const Iteration = models_1.IterationModel.initModel(sequelize);
44
47
  const League = models_1.LeagueModel.initModel(sequelize);
@@ -83,6 +86,7 @@ function initModels(sequelize) {
83
86
  Notification.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
84
87
  AuthUser.hasMany(GachaPullHistory, { as: 'GachaPullHistory', foreignKey: 'user_id' });
85
88
  AuthUser.hasMany(UserPullGrant, { as: 'UserPullGrants', foreignKey: 'user_id' });
89
+ AuthUser.hasMany(PremiumGrant, { as: 'PremiumGrants', foreignKey: 'user_id' });
86
90
  AuthIdentity.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
87
91
  AuthSession.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
88
92
  UserTeam.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
@@ -93,6 +97,8 @@ function initModels(sequelize) {
93
97
  GachaPullHistory.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
94
98
  GachaPullHistory.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
95
99
  UserPullGrant.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
100
+ PremiumGrant.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
101
+ PremiumGrant.belongsTo(AuthUser, { as: 'grantedByUser', foreignKey: 'granted_by' });
96
102
  Wishlist.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
97
103
  UserSettings.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
98
104
  Competition.belongsTo(Iteration, { as: 'Iteration', foreignKey: 'iteration' });
@@ -110,6 +116,8 @@ function initModels(sequelize) {
110
116
  });
111
117
  CompetitionChampion.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
112
118
  CompetitionChampion.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
119
+ JerseyBadge.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
120
+ JerseyBadge.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
113
121
  CompetitionMVP.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
114
122
  CompetitionMVP.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
115
123
  CompetitionMatch.belongsTo(Competition, { as: 'Competition', foreignKey: 'competition_id' });
@@ -228,6 +236,10 @@ function initModels(sequelize) {
228
236
  Team.hasOne(Physiotherapist, { as: 'Physiotherapist', foreignKey: 'team_id' });
229
237
  Physiotherapist.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
230
238
  Team.hasOne(Tactics, { as: 'tactics', foreignKey: 'team_id' });
239
+ // Saved premium kit. Like lineup presets, deliberately NOT in Team.defaultScope: only the management API (editor)
240
+ // and the effective-jersey resolver include it, so the simulator's team loads are unchanged.
241
+ Team.hasOne(TeamJersey, { as: 'TeamJersey', foreignKey: 'team_id' });
242
+ TeamJersey.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
231
243
  // Lineup presets are loaded only by the management API; deliberately NOT in Team.defaultScope so the
232
244
  // simulator's team loads are unchanged (it reads the active config from the mirrored Tactics row).
233
245
  Team.hasMany(LineupPreset, { as: 'lineupPresets', foreignKey: 'team_id' });
@@ -309,6 +321,7 @@ function initModels(sequelize) {
309
321
  AuthUser,
310
322
  Competition,
311
323
  CompetitionChampion,
324
+ JerseyBadge,
312
325
  CompetitionMatch,
313
326
  CompetitionMVP,
314
327
  CompetitionStandings,
@@ -354,6 +367,7 @@ function initModels(sequelize) {
354
367
  GachaPity,
355
368
  GachaPullHistory,
356
369
  UserPullGrant,
370
+ PremiumGrant,
357
371
  Wishlist,
358
372
  UserSettings,
359
373
  UserDeviceToken,
@@ -14,7 +14,7 @@ export interface AuthIdentityAttributes {
14
14
  }
15
15
  export type AuthIdentityPk = 'identity_id';
16
16
  export type AuthIdentityId = AuthIdentityModel[AuthIdentityPk];
17
- export type AuthProvider = 'EMAIL' | 'GOOGLE' | 'FACEBOOK' | 'APPLE';
17
+ export type AuthProvider = 'EMAIL' | 'GOOGLE' | 'FACEBOOK' | 'APPLE' | 'PATREON';
18
18
  export type AuthIdentityOptionalAttributes = 'email' | 'password_hash' | 'last_used_at' | 'created_at' | 'updated_at';
19
19
  export type AuthIdentityCreationAttributes = Optional<AuthIdentityAttributes, AuthIdentityOptionalAttributes>;
20
20
  export declare class AuthIdentityModel extends Model<AuthIdentityAttributes, AuthIdentityCreationAttributes> implements AuthIdentityAttributes {
@@ -19,7 +19,7 @@ class AuthIdentityModel extends sequelize_1.Model {
19
19
  }
20
20
  },
21
21
  provider: {
22
- type: sequelize_1.DataTypes.ENUM('EMAIL', 'GOOGLE', 'FACEBOOK', 'APPLE'),
22
+ type: sequelize_1.DataTypes.ENUM('EMAIL', 'GOOGLE', 'FACEBOOK', 'APPLE', 'PATREON'),
23
23
  allowNull: false
24
24
  },
25
25
  provider_user_id: {
@@ -7,6 +7,7 @@ export * from './competition';
7
7
  export * from './competition-mvp';
8
8
  export * from './competition-champion';
9
9
  export * from './competition-match';
10
+ export * from './jersey-badge';
10
11
  export * from './competition-standings';
11
12
  export * from './competition-standings-match';
12
13
  export * from './competition-teams';
@@ -37,8 +38,10 @@ export * from './physiotherapist';
37
38
  export * from './team';
38
39
  export * from './team-replacement';
39
40
  export * from './tactics';
41
+ export * from './team-jersey';
40
42
  export * from './lineup-preset';
41
43
  export * from './base-position-preset';
44
+ export * from './premium-grant';
42
45
  export * from './event';
43
46
  export * from './event-competition';
44
47
  export * from './event-bot-team';
@@ -23,6 +23,7 @@ __exportStar(require("./competition"), exports);
23
23
  __exportStar(require("./competition-mvp"), exports);
24
24
  __exportStar(require("./competition-champion"), exports);
25
25
  __exportStar(require("./competition-match"), exports);
26
+ __exportStar(require("./jersey-badge"), exports);
26
27
  __exportStar(require("./competition-standings"), exports);
27
28
  __exportStar(require("./competition-standings-match"), exports);
28
29
  __exportStar(require("./competition-teams"), exports);
@@ -53,8 +54,10 @@ __exportStar(require("./physiotherapist"), exports);
53
54
  __exportStar(require("./team"), exports);
54
55
  __exportStar(require("./team-replacement"), exports);
55
56
  __exportStar(require("./tactics"), exports);
57
+ __exportStar(require("./team-jersey"), exports);
56
58
  __exportStar(require("./lineup-preset"), exports);
57
59
  __exportStar(require("./base-position-preset"), exports);
60
+ __exportStar(require("./premium-grant"), exports);
58
61
  __exportStar(require("./event"), exports);
59
62
  __exportStar(require("./event-competition"), exports);
60
63
  __exportStar(require("./event-bot-team"), exports);
@@ -0,0 +1,32 @@
1
+ import * as Sequelize from 'sequelize';
2
+ import { Model } from 'sequelize';
3
+ import { CompetitionId, CompetitionModel, TeamId, TeamModel } from '.';
4
+ export type JerseyBadgeKind = 'LEAGUE' | 'NATIONALS' | 'REGIONALS' | 'WCC';
5
+ export interface JerseyBadgeAttributes {
6
+ competition_id: string;
7
+ team_id: string;
8
+ badge: JerseyBadgeKind;
9
+ region: string | null;
10
+ start_time: Date;
11
+ end_time: Date;
12
+ }
13
+ export type JerseyBadgePk = 'competition_id';
14
+ export type JerseyBadgeId = JerseyBadgeModel[JerseyBadgePk];
15
+ export type JerseyBadgeCreationAttributes = JerseyBadgeAttributes;
16
+ export declare class JerseyBadgeModel extends Model<JerseyBadgeAttributes, JerseyBadgeCreationAttributes> implements JerseyBadgeAttributes {
17
+ competition_id: string;
18
+ team_id: string;
19
+ badge: JerseyBadgeKind;
20
+ region: string | null;
21
+ start_time: Date;
22
+ end_time: Date;
23
+ competition: CompetitionModel;
24
+ getCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
25
+ setCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
26
+ createCompetition: Sequelize.BelongsToCreateAssociationMixin<CompetitionModel>;
27
+ team: TeamModel;
28
+ getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
29
+ setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
30
+ createTeam: Sequelize.BelongsToCreateAssociationMixin<TeamModel>;
31
+ static initModel(sequelize: Sequelize.Sequelize): typeof JerseyBadgeModel;
32
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JerseyBadgeModel = void 0;
4
+ const sequelize_1 = require("sequelize");
5
+ class JerseyBadgeModel extends sequelize_1.Model {
6
+ static initModel(sequelize) {
7
+ return JerseyBadgeModel.init({
8
+ competition_id: {
9
+ type: sequelize_1.DataTypes.UUID,
10
+ allowNull: false,
11
+ primaryKey: true,
12
+ references: {
13
+ model: 'Competition',
14
+ key: 'competition_id'
15
+ }
16
+ },
17
+ team_id: {
18
+ type: sequelize_1.DataTypes.UUID,
19
+ allowNull: false,
20
+ references: {
21
+ model: 'Team',
22
+ key: 'team_id'
23
+ }
24
+ },
25
+ badge: {
26
+ type: sequelize_1.DataTypes.ENUM('LEAGUE', 'NATIONALS', 'REGIONALS', 'WCC'),
27
+ allowNull: false
28
+ },
29
+ region: {
30
+ type: sequelize_1.DataTypes.STRING(3),
31
+ allowNull: true
32
+ },
33
+ start_time: {
34
+ type: sequelize_1.DataTypes.DATE,
35
+ allowNull: false
36
+ },
37
+ end_time: {
38
+ type: sequelize_1.DataTypes.DATE,
39
+ allowNull: false
40
+ }
41
+ }, {
42
+ sequelize,
43
+ tableName: 'JerseyBadge',
44
+ schema: 'public',
45
+ timestamps: false,
46
+ indexes: [
47
+ {
48
+ name: 'jerseybadge_pk',
49
+ unique: true,
50
+ fields: [{ name: 'competition_id' }]
51
+ },
52
+ {
53
+ name: 'JerseyBadge_team_active_idx',
54
+ fields: [{ name: 'team_id' }, { name: 'start_time' }, { name: 'end_time' }]
55
+ }
56
+ ]
57
+ });
58
+ }
59
+ }
60
+ exports.JerseyBadgeModel = JerseyBadgeModel;
@@ -0,0 +1,39 @@
1
+ import * as Sequelize from 'sequelize';
2
+ import { Model, Optional } from 'sequelize';
3
+ import { PremiumGrantSource } from '../../service';
4
+ import { AuthUserId, AuthUserModel } from '.';
5
+ export interface PremiumGrantAttributes {
6
+ grant_id: string;
7
+ user_id: string;
8
+ tier: number;
9
+ source: PremiumGrantSource;
10
+ granted_by?: string | null;
11
+ granted_at: Date;
12
+ expires_at?: Date | null;
13
+ revoked_at?: Date | null;
14
+ patreon_member_id?: string | null;
15
+ note?: string | null;
16
+ }
17
+ export type PremiumGrantPk = 'grant_id';
18
+ export type PremiumGrantId = PremiumGrantModel[PremiumGrantPk];
19
+ export type PremiumGrantOptionalAttributes = 'grant_id' | 'granted_by' | 'granted_at' | 'expires_at' | 'revoked_at' | 'patreon_member_id' | 'note';
20
+ export type PremiumGrantCreationAttributes = Optional<PremiumGrantAttributes, PremiumGrantOptionalAttributes>;
21
+ export declare class PremiumGrantModel extends Model<PremiumGrantAttributes, PremiumGrantCreationAttributes> implements PremiumGrantAttributes {
22
+ grant_id: string;
23
+ user_id: string;
24
+ tier: number;
25
+ source: PremiumGrantSource;
26
+ granted_by?: string | null;
27
+ granted_at: Date;
28
+ expires_at?: Date | null;
29
+ revoked_at?: Date | null;
30
+ patreon_member_id?: string | null;
31
+ note?: string | null;
32
+ user: AuthUserModel;
33
+ getUser: Sequelize.BelongsToGetAssociationMixin<AuthUserModel>;
34
+ setUser: Sequelize.BelongsToSetAssociationMixin<AuthUserModel, AuthUserId>;
35
+ createUser: Sequelize.BelongsToCreateAssociationMixin<AuthUserModel>;
36
+ grantedByUser?: AuthUserModel | null;
37
+ getGrantedByUser: Sequelize.BelongsToGetAssociationMixin<AuthUserModel>;
38
+ static initModel(sequelize: Sequelize.Sequelize): typeof PremiumGrantModel;
39
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PremiumGrantModel = void 0;
4
+ const sequelize_1 = require("sequelize");
5
+ class PremiumGrantModel extends sequelize_1.Model {
6
+ static initModel(sequelize) {
7
+ return PremiumGrantModel.init({
8
+ grant_id: {
9
+ type: sequelize_1.DataTypes.UUID,
10
+ allowNull: false,
11
+ primaryKey: true,
12
+ defaultValue: sequelize_1.DataTypes.UUIDV4
13
+ },
14
+ user_id: {
15
+ type: sequelize_1.DataTypes.UUID,
16
+ allowNull: false,
17
+ references: {
18
+ model: 'AuthUser',
19
+ key: 'user_id'
20
+ }
21
+ },
22
+ tier: {
23
+ type: sequelize_1.DataTypes.INTEGER,
24
+ allowNull: false
25
+ },
26
+ source: {
27
+ type: sequelize_1.DataTypes.ENUM('ADMIN', 'PATREON'),
28
+ allowNull: false
29
+ },
30
+ granted_by: {
31
+ type: sequelize_1.DataTypes.UUID,
32
+ allowNull: true,
33
+ references: {
34
+ model: 'AuthUser',
35
+ key: 'user_id'
36
+ }
37
+ },
38
+ granted_at: {
39
+ type: sequelize_1.DataTypes.DATE,
40
+ allowNull: false,
41
+ defaultValue: sequelize_1.DataTypes.NOW
42
+ },
43
+ expires_at: {
44
+ type: sequelize_1.DataTypes.DATE,
45
+ allowNull: true
46
+ },
47
+ revoked_at: {
48
+ type: sequelize_1.DataTypes.DATE,
49
+ allowNull: true
50
+ },
51
+ patreon_member_id: {
52
+ type: sequelize_1.DataTypes.STRING,
53
+ allowNull: true
54
+ },
55
+ note: {
56
+ type: sequelize_1.DataTypes.TEXT,
57
+ allowNull: true
58
+ }
59
+ }, {
60
+ sequelize,
61
+ tableName: 'PremiumGrant',
62
+ schema: 'public',
63
+ timestamps: false,
64
+ indexes: [
65
+ {
66
+ name: 'PremiumGrant_pk',
67
+ unique: true,
68
+ fields: [{ name: 'grant_id' }]
69
+ },
70
+ {
71
+ name: 'PremiumGrant_user_id_idx',
72
+ fields: [{ name: 'user_id' }]
73
+ },
74
+ {
75
+ name: 'PremiumGrant_patreon_member_id_idx',
76
+ fields: [{ name: 'patreon_member_id' }]
77
+ }
78
+ ]
79
+ });
80
+ }
81
+ }
82
+ exports.PremiumGrantModel = PremiumGrantModel;
@@ -0,0 +1,20 @@
1
+ import * as Sequelize from 'sequelize';
2
+ import { Model } from 'sequelize';
3
+ import { TeamId, TeamModel } from '.';
4
+ import { JerseyConfig } from '../../service';
5
+ export interface TeamJerseyAttributes {
6
+ team_id: string;
7
+ config: JerseyConfig;
8
+ }
9
+ export type TeamJerseyPk = 'team_id';
10
+ export type TeamJerseyId = TeamJerseyModel[TeamJerseyPk];
11
+ export type TeamJerseyCreationAttributes = TeamJerseyAttributes;
12
+ export declare class TeamJerseyModel extends Model<TeamJerseyAttributes, TeamJerseyCreationAttributes> implements TeamJerseyAttributes {
13
+ team_id: string;
14
+ config: JerseyConfig;
15
+ team: TeamModel;
16
+ getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
17
+ setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
18
+ createTeam: Sequelize.BelongsToCreateAssociationMixin<TeamModel>;
19
+ static initModel(sequelize: Sequelize.Sequelize): typeof TeamJerseyModel;
20
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TeamJerseyModel = void 0;
4
+ const sequelize_1 = require("sequelize");
5
+ class TeamJerseyModel extends sequelize_1.Model {
6
+ static initModel(sequelize) {
7
+ return TeamJerseyModel.init({
8
+ team_id: {
9
+ type: sequelize_1.DataTypes.UUID,
10
+ allowNull: false,
11
+ primaryKey: true,
12
+ references: {
13
+ model: 'Team',
14
+ key: 'team_id'
15
+ }
16
+ },
17
+ config: {
18
+ type: sequelize_1.DataTypes.JSONB,
19
+ allowNull: false
20
+ }
21
+ }, {
22
+ sequelize,
23
+ tableName: 'TeamJersey',
24
+ schema: 'public',
25
+ timestamps: false,
26
+ indexes: [
27
+ {
28
+ name: 'TeamJersey_pk',
29
+ unique: true,
30
+ fields: [
31
+ { name: 'team_id' }
32
+ ]
33
+ }
34
+ ]
35
+ });
36
+ }
37
+ }
38
+ exports.TeamJerseyModel = TeamJerseyModel;
@@ -1,6 +1,6 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model, Optional } from 'sequelize';
3
- import { CompetitionChampionId, CompetitionChampionModel, CompetitionId, CompetitionModel, CompetitionTeamsId, CompetitionTeamsModel, CountryId, CountryModel, DivisionAttributes, DivisionId, DivisionModel, LegacyTeamFlagModel, MatchId, MatchModel, MatchRatingId, MatchRatingModel, PlayerAttributes, PlayerId, PlayerModel, PlayerTeamId, PlayerTeamModel, RallyId, RallyModel, TacticsAttributes, TacticsId, TacticsModel } from '.';
3
+ import { CompetitionChampionId, CompetitionChampionModel, CompetitionId, CompetitionModel, CompetitionTeamsId, CompetitionTeamsModel, CountryId, CountryModel, DivisionAttributes, DivisionId, DivisionModel, LegacyTeamFlagModel, MatchId, MatchModel, MatchRatingId, MatchRatingModel, PlayerAttributes, PlayerId, PlayerModel, PlayerTeamId, PlayerTeamModel, RallyId, RallyModel, TacticsAttributes, TacticsId, TacticsModel, TeamJerseyModel } from '.';
4
4
  export interface TeamAttributes {
5
5
  team_id: string;
6
6
  name: string;
@@ -29,6 +29,8 @@ export declare class TeamModel extends Model<TeamAttributes, TeamCreationAttribu
29
29
  getTactics: Sequelize.HasOneGetAssociationMixin<TacticsModel>;
30
30
  setTactics: Sequelize.HasOneSetAssociationMixin<TacticsModel, TacticsId>;
31
31
  createTactics: Sequelize.HasOneCreateAssociationMixin<TacticsModel>;
32
+ TeamJersey?: TeamJerseyModel;
33
+ getTeamJersey: Sequelize.HasOneGetAssociationMixin<TeamJerseyModel>;
32
34
  country: CountryModel;
33
35
  getCountry: Sequelize.BelongsToGetAssociationMixin<CountryModel>;
34
36
  setCountry: Sequelize.BelongsToSetAssociationMixin<CountryModel, CountryId>;
@@ -50,6 +50,9 @@ function transformToObject(model, currentIteration, tacticsOverride) {
50
50
  rating: model.rating,
51
51
  tactics,
52
52
  roster,
53
- divisionId: model.division_id
53
+ divisionId: model.division_id,
54
+ // Present only when the caller explicitly included the TeamJersey association (editor / effective-jersey
55
+ // resolver); undefined on ordinary team loads, which heals to the id-derived default in the UI.
56
+ jerseyConfig: model.TeamJersey?.config
54
57
  });
55
58
  }
@@ -5,4 +5,5 @@ export * from './event';
5
5
  export * from './player';
6
6
  export * from './competition';
7
7
  export * from './notification';
8
+ export * from './premium';
8
9
  export * from './utils';
@@ -21,4 +21,5 @@ __exportStar(require("./event"), exports);
21
21
  __exportStar(require("./player"), exports);
22
22
  __exportStar(require("./competition"), exports);
23
23
  __exportStar(require("./notification"), exports);
24
+ __exportStar(require("./premium"), exports);
24
25
  __exportStar(require("./utils"), exports);
@@ -194,8 +194,8 @@ function allStats(value) {
194
194
  const cap = (0, improvement_1.getStatCap)(rarity_1.RarityEnum.COMMON); // 59
195
195
  (0, globals_1.expect)((0, improvement_1.selectImprovableStat)(rarity_1.RarityEnum.COMMON, stats_1.StatsEnum.ATTACK, allStats(cap))).toBeNull();
196
196
  });
197
- (0, globals_1.it)('returns a stat from the SERVE focus pool (serve, awareness, attack)', () => {
198
- const focusPool = new Set(['serve', 'awareness', 'attack']);
197
+ (0, globals_1.it)('returns a stat from the SERVE focus pool (serve, awareness, focus, attack)', () => {
198
+ const focusPool = new Set(['serve', 'awareness', 'focus', 'attack']);
199
199
  const stats = allStats(1);
200
200
  // Run many times to confirm we never escape the focus pool
201
201
  for (let i = 0; i < 100; i++) {
@@ -215,13 +215,13 @@ function allStats(value) {
215
215
  });
216
216
  (0, globals_1.it)('falls back to any improvable stat when all focus-pool stats are capped', () => {
217
217
  const cap = (0, improvement_1.getStatCap)(rarity_1.RarityEnum.COMMON);
218
- // Cap the SERVE focus stats: serve, awareness, attack
219
- const stats = { ...allStats(1), serve: cap, awareness: cap, attack: cap };
218
+ // Cap the SERVE focus stats: serve, awareness, focus, attack
219
+ const stats = { ...allStats(1), serve: cap, awareness: cap, focus: cap, attack: cap };
220
220
  for (let i = 0; i < 50; i++) {
221
221
  const result = (0, improvement_1.selectImprovableStat)(rarity_1.RarityEnum.COMMON, stats_1.StatsEnum.SERVE, stats);
222
222
  (0, globals_1.expect)(result).not.toBeNull();
223
223
  // Must never return one of the capped focus stats
224
- (0, globals_1.expect)(['serve', 'awareness', 'attack']).not.toContain(result);
224
+ (0, globals_1.expect)(['serve', 'awareness', 'focus', 'attack']).not.toContain(result);
225
225
  }
226
226
  });
227
227
  (0, globals_1.it)('returns a string (DB column name) not a PerformanceStatsKey', () => {
@@ -22,12 +22,14 @@ function getMultipliers(statName, performanceStats) {
22
22
  switch (statName) {
23
23
  case StatsEnum.ATTACK:
24
24
  return {
25
- spike: 0.55,
25
+ spike: 0.5,
26
26
  backAttack: 0.1,
27
27
  power: performanceStats == null ? 0.125 : performanceStats.power >= performanceStats.quick ? 0.2 : 0.05,
28
28
  quick: performanceStats == null ? 0.125 : performanceStats.quick > performanceStats.power ? 0.2 : 0.05,
29
29
  awareness: 0.05,
30
- attack: 0.05
30
+ attack: 0.05,
31
+ // focus places the spike inside its aim cone (attack-shot.focusLanding): placement precision, kill vs dug.
32
+ focus: 0.05
31
33
  };
32
34
  case StatsEnum.SET:
33
35
  return {
@@ -36,33 +38,45 @@ function getMultipliers(statName, performanceStats) {
36
38
  // rating AND in the SET training pool. Without it a setter on SET focus can never raise `quick` (it has
37
39
  // weight 0 in every role too), and their whole tempo game would be fixed forever by a generation roll.
38
40
  quick: 0.07,
39
- overhand: 0.1,
40
- awareness: 0.05,
41
+ // set TECHNIQUE: overhand sets good passes, bump sets scramble/negative passes (both +/-15% on set score).
42
+ overhand: 0.08,
43
+ bump: 0.07,
44
+ // attack is the setter's crit-style upside on the set (setAttackMod in the engine, owner 2026-08-17), the
45
+ // same modifier the spike + serve use. awareness dropped: the engine reads it only for the setter DUMP.
41
46
  attack: 0.05
42
47
  };
43
48
  case StatsEnum.RECEIVE:
49
+ // reflex carries the most (owner 2026-08-19): reception success is gated by REFLEX (the reach + lunge-save on
50
+ // any serve that lands out of position), not by the `reception` control stat. A low-reflex passer is aced
51
+ // regardless of reception -- match 3aebe480 had a Mythic (reception 88, reflex 36) shank 79% of receptions,
52
+ // and the league >=40%-ace group averages reflex 24-32 across every rarity. `reception` is the pass CONTROL
53
+ // once the ball is reached; bump/overhand are technique; defense is the pass variance; focus avoids the seam ace.
44
54
  return {
45
- reception: 0.75,
46
- overhand: 0.015,
47
- bump: 0.15,
48
- focus: 0.015,
49
- defense: 0.055,
50
- reflex: 0.015
55
+ reflex: 0.45,
56
+ reception: 0.3,
57
+ bump: 0.1,
58
+ overhand: 0.05,
59
+ defense: 0.05,
60
+ focus: 0.05
51
61
  };
52
62
  case StatsEnum.SERVE:
53
63
  return {
54
- serve: 0.85,
55
- awareness: 0.075,
56
- attack: 0.075
64
+ serve: 0.75,
65
+ // awareness (target selection) + focus (aim-cone placement) are important serve drivers (owner 2026-08-17).
66
+ awareness: 0.1,
67
+ focus: 0.1,
68
+ attack: 0.05
57
69
  };
58
70
  case StatsEnum.BLOCK:
59
71
  return {
60
- block: 0.65,
72
+ block: 0.6,
61
73
  read: performanceStats == null ? 0.125 : performanceStats.read >= performanceStats.commit ? 0.2 : 0.05,
62
74
  commit: performanceStats == null ? 0.125 : performanceStats.commit > performanceStats.read ? 0.2 : 0.05,
63
- focus: 0.04,
75
+ // reflex sets block REACH (help blockers forming doubles/triples; dominant for surprise/dump blocks): raised
76
+ // from 0.02 to match its impact.
77
+ reflex: 0.08,
64
78
  defense: 0.04,
65
- reflex: 0.02
79
+ focus: 0.03
66
80
  };
67
81
  case StatsEnum.STAMINA:
68
82
  return { stamina: 1 };
@@ -64,10 +64,10 @@ function makeStats(base = 50, overrides = {}) {
64
64
  const total = Object.values(m).reduce((s, v) => s + (v ?? 0), 0);
65
65
  (0, globals_1.expect)(total).toBeCloseTo(1, 10);
66
66
  });
67
- (0, globals_1.it)('reception has the highest weight (0.75)', () => {
67
+ (0, globals_1.it)('reflex has the highest weight (reflex gates the reach + lunge-save)', () => {
68
68
  const m = (0, stats_1.getMultipliers)(stats_1.StatsEnum.RECEIVE);
69
69
  const max = Math.max(...Object.values(m).map(v => v ?? 0));
70
- (0, globals_1.expect)(m.reception).toBe(max);
70
+ (0, globals_1.expect)(m.reflex).toBe(max);
71
71
  });
72
72
  });
73
73
  (0, globals_1.describe)('SERVE', () => {
@@ -147,11 +147,11 @@ function makeStats(base = 50, overrides = {}) {
147
147
  const strong = (0, stats_1.calculateStatScore)(makeStats(80), stats_1.StatsEnum.SERVE);
148
148
  (0, globals_1.expect)(strong).toBeGreaterThan(weak);
149
149
  });
150
- (0, globals_1.it)('SERVE: serve=99 dominates, contributing 0.85 of the total', () => {
151
- // All other serve-relevant stats at 1: awareness=1, attack=1
152
- // score ≈ 99*0.85 + 1*0.075 + 1*0.075 = 84.15 + 0.15 = 84.384
150
+ (0, globals_1.it)('SERVE: serve=99 dominates, contributing 0.75 of the total', () => {
151
+ // All other serve-relevant stats at 1: awareness=1, focus=1, attack=1
152
+ // score ≈ 99*0.75 + 1*0.1 + 1*0.1 + 1*0.05 = 74.25 + 0.25 = 74.574
153
153
  const stats = makeStats(1, { serve: 99 });
154
- (0, globals_1.expect)((0, stats_1.calculateStatScore)(stats, stats_1.StatsEnum.SERVE)).toBe(84);
154
+ (0, globals_1.expect)((0, stats_1.calculateStatScore)(stats, stats_1.StatsEnum.SERVE)).toBe(74);
155
155
  });
156
156
  });
157
157
  // ─── invariants (every stat, every branch) ────────────────────────────────────
@@ -0,0 +1,3 @@
1
+ export * from './premium-tier';
2
+ export * from './premium-grant';
3
+ export * from './patreon-tier-map';