volleyballsimtypes 0.0.476 → 0.0.477

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 (27) hide show
  1. package/dist/cjs/src/api/index.d.ts +9 -1
  2. package/dist/cjs/src/data/models/auth-session.d.ts +5 -1
  3. package/dist/cjs/src/data/models/auth-session.js +8 -0
  4. package/dist/cjs/src/data/models/tactics.d.ts +9 -1
  5. package/dist/cjs/src/data/transformers/tactics.js +33 -3
  6. package/dist/cjs/src/data/transformers/tactics.test.js +31 -0
  7. package/dist/cjs/src/service/team/designated-sub.d.ts +5 -0
  8. package/dist/cjs/src/service/team/schemas/designated-sub.z.d.ts +109 -0
  9. package/dist/cjs/src/service/team/schemas/designated-sub.z.js +15 -3
  10. package/dist/cjs/src/service/team/schemas/designated-sub.z.test.js +73 -0
  11. package/dist/cjs/src/service/team/schemas/libero-sub.z.d.ts +4 -4
  12. package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +72 -2
  13. package/dist/cjs/src/service/team/schemas/team.z.d.ts +72 -2
  14. package/dist/esm/src/api/index.d.ts +9 -1
  15. package/dist/esm/src/data/models/auth-session.d.ts +5 -1
  16. package/dist/esm/src/data/models/auth-session.js +8 -0
  17. package/dist/esm/src/data/models/tactics.d.ts +9 -1
  18. package/dist/esm/src/data/transformers/tactics.js +33 -3
  19. package/dist/esm/src/data/transformers/tactics.test.js +31 -0
  20. package/dist/esm/src/service/team/designated-sub.d.ts +5 -0
  21. package/dist/esm/src/service/team/schemas/designated-sub.z.d.ts +109 -0
  22. package/dist/esm/src/service/team/schemas/designated-sub.z.js +14 -2
  23. package/dist/esm/src/service/team/schemas/designated-sub.z.test.js +73 -0
  24. package/dist/esm/src/service/team/schemas/libero-sub.z.d.ts +4 -4
  25. package/dist/esm/src/service/team/schemas/tactics.z.d.ts +72 -2
  26. package/dist/esm/src/service/team/schemas/team.z.d.ts +72 -2
  27. package/package.json +1 -1
@@ -63,6 +63,41 @@ export declare const TeamInputSchema: z.ZodObject<{
63
63
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
64
64
  }, z.core.$strip>], "type">>>;
65
65
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
66
+ pinchServer: z.ZodOptional<z.ZodBoolean>;
67
+ subBackMode: z.ZodOptional<z.ZodEnum<{
68
+ FATIGUE: "FATIGUE";
69
+ CONDITIONS: "CONDITIONS";
70
+ }>>;
71
+ subBackConditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
72
+ type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
73
+ }, z.core.$strip>, z.ZodObject<{
74
+ type: z.ZodLiteral<import("..").PinchConditionType.TEAM_SET_POINT>;
75
+ margin: z.ZodNumber;
76
+ opponent: z.ZodEnum<typeof import("..").OpponentRelation>;
77
+ }, z.core.$strip>, z.ZodObject<{
78
+ type: z.ZodLiteral<import("..").PinchConditionType.OPPONENT_SET_POINT>;
79
+ margin: z.ZodNumber;
80
+ }, z.core.$strip>, z.ZodObject<{
81
+ type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
82
+ direction: z.ZodEnum<typeof import("..").ScoreDirection>;
83
+ points: z.ZodNumber;
84
+ }, z.core.$strip>, z.ZodObject<{
85
+ type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
86
+ rotations: z.ZodNumber;
87
+ }, z.core.$strip>, z.ZodObject<{
88
+ type: z.ZodLiteral<import("..").PinchConditionType.FROM_SET>;
89
+ set: z.ZodNumber;
90
+ }, z.core.$strip>, z.ZodObject<{
91
+ type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
92
+ score: z.ZodNumber;
93
+ }, z.core.$strip>, z.ZodObject<{
94
+ type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
95
+ score: z.ZodNumber;
96
+ }, z.core.$strip>, z.ZodObject<{
97
+ type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
98
+ phase: z.ZodEnum<typeof import("..").MatchPhase>;
99
+ }, z.core.$strip>], "type">>>;
100
+ subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
66
101
  }, z.core.$strip>>>;
67
102
  mode: z.ZodEnum<{
68
103
  NEVER: "NEVER";
@@ -102,6 +137,41 @@ export declare const TeamInputSchema: z.ZodObject<{
102
137
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
103
138
  }, z.core.$strip>], "type">>>;
104
139
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
140
+ pinchServer: z.ZodOptional<z.ZodBoolean>;
141
+ subBackMode: z.ZodOptional<z.ZodEnum<{
142
+ FATIGUE: "FATIGUE";
143
+ CONDITIONS: "CONDITIONS";
144
+ }>>;
145
+ subBackConditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
146
+ type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
147
+ }, z.core.$strip>, z.ZodObject<{
148
+ type: z.ZodLiteral<import("..").PinchConditionType.TEAM_SET_POINT>;
149
+ margin: z.ZodNumber;
150
+ opponent: z.ZodEnum<typeof import("..").OpponentRelation>;
151
+ }, z.core.$strip>, z.ZodObject<{
152
+ type: z.ZodLiteral<import("..").PinchConditionType.OPPONENT_SET_POINT>;
153
+ margin: z.ZodNumber;
154
+ }, z.core.$strip>, z.ZodObject<{
155
+ type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
156
+ direction: z.ZodEnum<typeof import("..").ScoreDirection>;
157
+ points: z.ZodNumber;
158
+ }, z.core.$strip>, z.ZodObject<{
159
+ type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
160
+ rotations: z.ZodNumber;
161
+ }, z.core.$strip>, z.ZodObject<{
162
+ type: z.ZodLiteral<import("..").PinchConditionType.FROM_SET>;
163
+ set: z.ZodNumber;
164
+ }, z.core.$strip>, z.ZodObject<{
165
+ type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
166
+ score: z.ZodNumber;
167
+ }, z.core.$strip>, z.ZodObject<{
168
+ type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
169
+ score: z.ZodNumber;
170
+ }, z.core.$strip>, z.ZodObject<{
171
+ type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
172
+ phase: z.ZodEnum<typeof import("..").MatchPhase>;
173
+ }, z.core.$strip>], "type">>>;
174
+ subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
105
175
  starter: z.ZodCustom<Player, Player>;
106
176
  }, z.core.$strip>>>;
107
177
  substitutionBand: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<typeof import("..").EnergyBand>, z.ZodLiteral<"NEVER">]>>;
@@ -111,8 +181,8 @@ export declare const TeamInputSchema: z.ZodObject<{
111
181
  mode: z.ZodEnum<{
112
182
  NEVER: "NEVER";
113
183
  FATIGUE: "FATIGUE";
114
- ALWAYS: "ALWAYS";
115
184
  CONDITIONS: "CONDITIONS";
185
+ ALWAYS: "ALWAYS";
116
186
  }>;
117
187
  fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
118
188
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -149,8 +219,8 @@ export declare const TeamInputSchema: z.ZodObject<{
149
219
  mode: z.ZodEnum<{
150
220
  NEVER: "NEVER";
151
221
  FATIGUE: "FATIGUE";
152
- ALWAYS: "ALWAYS";
153
222
  CONDITIONS: "CONDITIONS";
223
+ ALWAYS: "ALWAYS";
154
224
  }>;
155
225
  fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
156
226
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -1,4 +1,4 @@
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, CourtPosition, ConditionLogic, EnergyBand, LiberoSubMode, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../service';
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, CourtPosition, ConditionLogic, EnergyBand, LiberoSubMode, MatchForfeitType, PerformanceStats, PinchCondition, RotationSystemEnum, SubBackMode, SubBand, SubMode } from '../service';
2
2
  export type Rally = DataProps<_Rally> & {
3
3
  homePlayerPosition: PlayerPosition[];
4
4
  awayPlayerPosition: PlayerPosition[];
@@ -87,6 +87,10 @@ export interface ApiSetSubConfig {
87
87
  benchFatigueBand?: EnergyBand;
88
88
  conditions?: PinchCondition[];
89
89
  conditionLogic?: ConditionLogic;
90
+ pinchServer?: boolean;
91
+ subBackMode?: SubBackMode;
92
+ subBackConditions?: PinchCondition[];
93
+ subBackConditionLogic?: ConditionLogic;
90
94
  }
91
95
  export interface ApiDesignatedSub {
92
96
  starterId: string;
@@ -96,6 +100,10 @@ export interface ApiDesignatedSub {
96
100
  benchFatigueBand?: EnergyBand;
97
101
  conditions?: PinchCondition[];
98
102
  conditionLogic?: ConditionLogic;
103
+ pinchServer?: boolean;
104
+ subBackMode?: SubBackMode;
105
+ subBackConditions?: PinchCondition[];
106
+ subBackConditionLogic?: ConditionLogic;
99
107
  sets?: ApiSetSubConfig[];
100
108
  }
101
109
  export interface ApiOffensivePreference {
@@ -11,10 +11,12 @@ export interface AuthSessionAttributes {
11
11
  user_agent?: string | null;
12
12
  created_at?: Date;
13
13
  updated_at?: Date;
14
+ previous_refresh_token_hash?: string | null;
15
+ previous_token_expires_at?: Date | null;
14
16
  }
15
17
  export type AuthSessionPk = 'session_id';
16
18
  export type AuthSessionId = AuthSessionModel[AuthSessionPk];
17
- export type AuthSessionOptionalAttributes = 'revoked_at' | 'ip_address' | 'user_agent' | 'created_at' | 'updated_at';
19
+ export type AuthSessionOptionalAttributes = 'revoked_at' | 'ip_address' | 'user_agent' | 'created_at' | 'updated_at' | 'previous_refresh_token_hash' | 'previous_token_expires_at';
18
20
  export type AuthSessionCreationAttributes = Optional<AuthSessionAttributes, AuthSessionOptionalAttributes>;
19
21
  export declare class AuthSessionModel extends Model<AuthSessionAttributes, AuthSessionCreationAttributes> implements AuthSessionAttributes {
20
22
  session_id: string;
@@ -26,6 +28,8 @@ export declare class AuthSessionModel extends Model<AuthSessionAttributes, AuthS
26
28
  user_agent?: string | null;
27
29
  created_at?: Date;
28
30
  updated_at?: Date;
31
+ previous_refresh_token_hash?: string | null;
32
+ previous_token_expires_at?: Date | null;
29
33
  user: AuthUserModel;
30
34
  getUser: Sequelize.BelongsToGetAssociationMixin<AuthUserModel>;
31
35
  setUser: Sequelize.BelongsToSetAssociationMixin<AuthUserModel, AuthUserId>;
@@ -44,6 +44,14 @@ export class AuthSessionModel extends Model {
44
44
  type: DataTypes.DATE,
45
45
  allowNull: false,
46
46
  defaultValue: DataTypes.NOW
47
+ },
48
+ previous_refresh_token_hash: {
49
+ type: DataTypes.STRING,
50
+ allowNull: true
51
+ },
52
+ previous_token_expires_at: {
53
+ type: DataTypes.DATE,
54
+ allowNull: true
47
55
  }
48
56
  }, {
49
57
  sequelize,
@@ -1,7 +1,7 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model, Optional } from 'sequelize';
3
3
  import { PlayerId, TeamId, TeamModel } from '.';
4
- import { ConditionLogic, CourtPosition, EnergyBand, LiberoSubMode, PinchCondition, RotationSystemEnum, SubBand, SubMode } from '../../service';
4
+ import { ConditionLogic, CourtPosition, EnergyBand, LiberoSubMode, PinchCondition, RotationSystemEnum, SubBackMode, SubBand, SubMode } from '../../service';
5
5
  export interface TacticsLineupAttributes {
6
6
  [CourtPosition.LIBERO_ZONE]?: PlayerId;
7
7
  [CourtPosition.LEFT_BACK]: PlayerId;
@@ -19,6 +19,10 @@ export interface SetSubConfigAttributes {
19
19
  benchFatigueBand?: EnergyBand;
20
20
  conditions?: PinchCondition[];
21
21
  conditionLogic?: ConditionLogic;
22
+ pinchServer?: boolean;
23
+ subBackMode?: SubBackMode;
24
+ subBackConditions?: PinchCondition[];
25
+ subBackConditionLogic?: ConditionLogic;
22
26
  }
23
27
  export interface DesignatedSubAttributes {
24
28
  starterId: PlayerId;
@@ -29,6 +33,10 @@ export interface DesignatedSubAttributes {
29
33
  benchFatigueBand?: EnergyBand;
30
34
  conditions?: PinchCondition[];
31
35
  conditionLogic?: ConditionLogic;
36
+ pinchServer?: boolean;
37
+ subBackMode?: SubBackMode;
38
+ subBackConditions?: PinchCondition[];
39
+ subBackConditionLogic?: ConditionLogic;
32
40
  sets?: SetSubConfigAttributes[];
33
41
  }
34
42
  export interface OffensivePreferenceAttributes {
@@ -11,6 +11,12 @@ function subModeFromAttributes(d) {
11
11
  return 'NEVER';
12
12
  return 'FATIGUE';
13
13
  }
14
+ // pinchServer only applies to PINCH ("Conditions") mode. A LEGACY PINCH row (no flag) defaults to true so
15
+ // existing pinch servers keep serve-and-return behavior; a new conditional sub persists an explicit false. This
16
+ // default must resolve identically on every READ path (both transformToObject and tacticsColumnsToApiDto).
17
+ function effectivePinchServer(mode, pinchServer) {
18
+ return mode === 'PINCH' ? (pinchServer ?? true) : undefined;
19
+ }
14
20
  function findPlayer(id, roster) {
15
21
  const player = roster.find((p) => p.id === id);
16
22
  if (player == null)
@@ -59,13 +65,21 @@ function transformToAttributes(tactics, teamId) {
59
65
  benchFatigueBand: ds.benchFatigueBand,
60
66
  conditions: ds.conditions,
61
67
  conditionLogic: ds.conditionLogic,
68
+ pinchServer: ds.pinchServer,
69
+ subBackMode: ds.subBackMode,
70
+ subBackConditions: ds.subBackConditions,
71
+ subBackConditionLogic: ds.subBackConditionLogic,
62
72
  sets: ds.sets?.map(s => ({
63
73
  mode: s.mode,
64
74
  benchId: s.bench?.id,
65
75
  fatigueBand: s.fatigueBand,
66
76
  benchFatigueBand: s.benchFatigueBand,
67
77
  conditions: s.conditions,
68
- conditionLogic: s.conditionLogic
78
+ conditionLogic: s.conditionLogic,
79
+ pinchServer: s.pinchServer,
80
+ subBackMode: s.subBackMode,
81
+ subBackConditions: s.subBackConditions,
82
+ subBackConditionLogic: s.subBackConditionLogic
69
83
  }))
70
84
  })),
71
85
  rotation_system: tactics.rotationSystem,
@@ -149,13 +163,21 @@ function transformToObject(model, roster) {
149
163
  benchFatigueBand: d.benchFatigueBand,
150
164
  conditions: d.conditions,
151
165
  conditionLogic: d.conditionLogic,
166
+ pinchServer: effectivePinchServer(subModeFromAttributes(d), d.pinchServer),
167
+ subBackMode: d.subBackMode,
168
+ subBackConditions: d.subBackConditions,
169
+ subBackConditionLogic: d.subBackConditionLogic,
152
170
  sets: d.sets?.map(s => ({
153
171
  mode: s.mode,
154
172
  bench: s.benchId != null ? findPlayer(s.benchId, roster) : undefined,
155
173
  fatigueBand: s.fatigueBand,
156
174
  benchFatigueBand: s.benchFatigueBand,
157
175
  conditions: s.conditions,
158
- conditionLogic: s.conditionLogic
176
+ conditionLogic: s.conditionLogic,
177
+ pinchServer: effectivePinchServer(s.mode, s.pinchServer),
178
+ subBackMode: s.subBackMode,
179
+ subBackConditions: s.subBackConditions,
180
+ subBackConditionLogic: s.subBackConditionLogic
159
181
  }))
160
182
  })),
161
183
  rotationSystem: model.rotation_system,
@@ -306,13 +328,21 @@ function tacticsColumnsToApiDto(model) {
306
328
  benchFatigueBand: d.benchFatigueBand,
307
329
  conditions: d.conditions,
308
330
  conditionLogic: d.conditionLogic,
331
+ pinchServer: effectivePinchServer(subModeFromAttributes(d), d.pinchServer),
332
+ subBackMode: d.subBackMode,
333
+ subBackConditions: d.subBackConditions,
334
+ subBackConditionLogic: d.subBackConditionLogic,
309
335
  sets: d.sets?.map(s => ({
310
336
  mode: s.mode,
311
337
  benchId: s.benchId,
312
338
  fatigueBand: s.fatigueBand,
313
339
  benchFatigueBand: s.benchFatigueBand,
314
340
  conditions: s.conditions,
315
- conditionLogic: s.conditionLogic
341
+ conditionLogic: s.conditionLogic,
342
+ pinchServer: effectivePinchServer(s.mode, s.pinchServer),
343
+ subBackMode: s.subBackMode,
344
+ subBackConditions: s.subBackConditions,
345
+ subBackConditionLogic: s.subBackConditionLogic
316
346
  }))
317
347
  })),
318
348
  rotationSystem: model.rotation_system,
@@ -58,6 +58,37 @@ describe('tacticsColumnsToApiDto()', () => {
58
58
  expect(dto.designatedSubs[0].fatigueBand).toBe(EnergyBand.WORN);
59
59
  expect(dto.designatedSubs[0].benchFatigueBand).toBe(EnergyBand.TIRED);
60
60
  });
61
+ it('defaults a legacy PINCH row (no pinchServer flag) to pinchServer true', () => {
62
+ const dto = tacticsColumnsToApiDto(makeTactics([
63
+ { starterId: 's1', benchId: 'b1', mode: 'PINCH', conditions: [{ type: 'ASAP' }] }
64
+ ]));
65
+ expect(dto.designatedSubs[0].pinchServer).toBe(true);
66
+ });
67
+ it('leaves pinchServer undefined for a non-PINCH row', () => {
68
+ const dto = tacticsColumnsToApiDto(makeTactics([
69
+ { starterId: 's1', benchId: 'b1', mode: 'FATIGUE', fatigueBand: EnergyBand.TIRED }
70
+ ]));
71
+ expect(dto.designatedSubs[0].pinchServer).toBeUndefined();
72
+ });
73
+ it('passes a conditional regular sub (pinchServer false + condition sub-back) through unchanged', () => {
74
+ const dto = tacticsColumnsToApiDto(makeTactics([
75
+ {
76
+ starterId: 's1',
77
+ benchId: 'b1',
78
+ mode: 'PINCH',
79
+ conditions: [{ type: 'SCORE_DIFF', direction: 'TRAILING', points: 3 }],
80
+ pinchServer: false,
81
+ subBackMode: 'CONDITIONS',
82
+ subBackConditions: [{ type: 'TEAM_SET_POINT', margin: 2, opponent: 'EITHER' }],
83
+ subBackConditionLogic: 'ANY'
84
+ }
85
+ ]));
86
+ const ds = dto.designatedSubs[0];
87
+ expect(ds.pinchServer).toBe(false);
88
+ expect(ds.subBackMode).toBe('CONDITIONS');
89
+ expect(ds.subBackConditions).toEqual([{ type: 'TEAM_SET_POINT', margin: 2, opponent: 'EITHER' }]);
90
+ expect(ds.subBackConditionLogic).toBe('ANY');
91
+ });
61
92
  it('drops a second libero that collides with the starting libero (not a reserve)', () => {
62
93
  const model = makeTactics([]);
63
94
  model.lineup[CourtPosition.LIBERO_ZONE] = 'libero1';
@@ -1,6 +1,7 @@
1
1
  import { Player } from '../player';
2
2
  import { EnergyBand, SubMode } from './energy-band';
3
3
  import { ConditionLogic, PinchCondition } from './pinch-condition';
4
+ export type SubBackMode = 'FATIGUE' | 'CONDITIONS';
4
5
  export interface SetSubConfig {
5
6
  readonly mode: SubMode;
6
7
  readonly bench?: Player;
@@ -8,6 +9,10 @@ export interface SetSubConfig {
8
9
  readonly benchFatigueBand?: EnergyBand;
9
10
  readonly conditions?: PinchCondition[];
10
11
  readonly conditionLogic?: ConditionLogic;
12
+ readonly pinchServer?: boolean;
13
+ readonly subBackMode?: SubBackMode;
14
+ readonly subBackConditions?: PinchCondition[];
15
+ readonly subBackConditionLogic?: ConditionLogic;
11
16
  }
12
17
  export interface DesignatedSub extends SetSubConfig {
13
18
  readonly starter: Player;
@@ -38,6 +38,10 @@ export declare const SubModeSchema: z.ZodEnum<{
38
38
  FATIGUE: "FATIGUE";
39
39
  PINCH: "PINCH";
40
40
  }>;
41
+ export declare const SubBackModeSchema: z.ZodEnum<{
42
+ FATIGUE: "FATIGUE";
43
+ CONDITIONS: "CONDITIONS";
44
+ }>;
41
45
  export declare const SetSubConfigSchema: z.ZodObject<{
42
46
  mode: z.ZodEnum<{
43
47
  NEVER: "NEVER";
@@ -77,6 +81,41 @@ export declare const SetSubConfigSchema: z.ZodObject<{
77
81
  phase: z.ZodEnum<typeof MatchPhase>;
78
82
  }, z.core.$strip>], "type">>>;
79
83
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
84
+ pinchServer: z.ZodOptional<z.ZodBoolean>;
85
+ subBackMode: z.ZodOptional<z.ZodEnum<{
86
+ FATIGUE: "FATIGUE";
87
+ CONDITIONS: "CONDITIONS";
88
+ }>>;
89
+ subBackConditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
90
+ type: z.ZodLiteral<PinchConditionType.ASAP>;
91
+ }, z.core.$strip>, z.ZodObject<{
92
+ type: z.ZodLiteral<PinchConditionType.TEAM_SET_POINT>;
93
+ margin: z.ZodNumber;
94
+ opponent: z.ZodEnum<typeof OpponentRelation>;
95
+ }, z.core.$strip>, z.ZodObject<{
96
+ type: z.ZodLiteral<PinchConditionType.OPPONENT_SET_POINT>;
97
+ margin: z.ZodNumber;
98
+ }, z.core.$strip>, z.ZodObject<{
99
+ type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
100
+ direction: z.ZodEnum<typeof ScoreDirection>;
101
+ points: z.ZodNumber;
102
+ }, z.core.$strip>, z.ZodObject<{
103
+ type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
104
+ rotations: z.ZodNumber;
105
+ }, z.core.$strip>, z.ZodObject<{
106
+ type: z.ZodLiteral<PinchConditionType.FROM_SET>;
107
+ set: z.ZodNumber;
108
+ }, z.core.$strip>, z.ZodObject<{
109
+ type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
110
+ score: z.ZodNumber;
111
+ }, z.core.$strip>, z.ZodObject<{
112
+ type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
113
+ score: z.ZodNumber;
114
+ }, z.core.$strip>, z.ZodObject<{
115
+ type: z.ZodLiteral<PinchConditionType.PHASE>;
116
+ phase: z.ZodEnum<typeof MatchPhase>;
117
+ }, z.core.$strip>], "type">>>;
118
+ subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
80
119
  }, z.core.$strip>;
81
120
  export declare const DesignatedSubSchema: z.ZodObject<{
82
121
  sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -118,6 +157,41 @@ export declare const DesignatedSubSchema: z.ZodObject<{
118
157
  phase: z.ZodEnum<typeof MatchPhase>;
119
158
  }, z.core.$strip>], "type">>>;
120
159
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
160
+ pinchServer: z.ZodOptional<z.ZodBoolean>;
161
+ subBackMode: z.ZodOptional<z.ZodEnum<{
162
+ FATIGUE: "FATIGUE";
163
+ CONDITIONS: "CONDITIONS";
164
+ }>>;
165
+ subBackConditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
166
+ type: z.ZodLiteral<PinchConditionType.ASAP>;
167
+ }, z.core.$strip>, z.ZodObject<{
168
+ type: z.ZodLiteral<PinchConditionType.TEAM_SET_POINT>;
169
+ margin: z.ZodNumber;
170
+ opponent: z.ZodEnum<typeof OpponentRelation>;
171
+ }, z.core.$strip>, z.ZodObject<{
172
+ type: z.ZodLiteral<PinchConditionType.OPPONENT_SET_POINT>;
173
+ margin: z.ZodNumber;
174
+ }, z.core.$strip>, z.ZodObject<{
175
+ type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
176
+ direction: z.ZodEnum<typeof ScoreDirection>;
177
+ points: z.ZodNumber;
178
+ }, z.core.$strip>, z.ZodObject<{
179
+ type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
180
+ rotations: z.ZodNumber;
181
+ }, z.core.$strip>, z.ZodObject<{
182
+ type: z.ZodLiteral<PinchConditionType.FROM_SET>;
183
+ set: z.ZodNumber;
184
+ }, z.core.$strip>, z.ZodObject<{
185
+ type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
186
+ score: z.ZodNumber;
187
+ }, z.core.$strip>, z.ZodObject<{
188
+ type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
189
+ score: z.ZodNumber;
190
+ }, z.core.$strip>, z.ZodObject<{
191
+ type: z.ZodLiteral<PinchConditionType.PHASE>;
192
+ phase: z.ZodEnum<typeof MatchPhase>;
193
+ }, z.core.$strip>], "type">>>;
194
+ subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
121
195
  }, z.core.$strip>>>;
122
196
  mode: z.ZodEnum<{
123
197
  NEVER: "NEVER";
@@ -157,5 +231,40 @@ export declare const DesignatedSubSchema: z.ZodObject<{
157
231
  phase: z.ZodEnum<typeof MatchPhase>;
158
232
  }, z.core.$strip>], "type">>>;
159
233
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
234
+ pinchServer: z.ZodOptional<z.ZodBoolean>;
235
+ subBackMode: z.ZodOptional<z.ZodEnum<{
236
+ FATIGUE: "FATIGUE";
237
+ CONDITIONS: "CONDITIONS";
238
+ }>>;
239
+ subBackConditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
240
+ type: z.ZodLiteral<PinchConditionType.ASAP>;
241
+ }, z.core.$strip>, z.ZodObject<{
242
+ type: z.ZodLiteral<PinchConditionType.TEAM_SET_POINT>;
243
+ margin: z.ZodNumber;
244
+ opponent: z.ZodEnum<typeof OpponentRelation>;
245
+ }, z.core.$strip>, z.ZodObject<{
246
+ type: z.ZodLiteral<PinchConditionType.OPPONENT_SET_POINT>;
247
+ margin: z.ZodNumber;
248
+ }, z.core.$strip>, z.ZodObject<{
249
+ type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
250
+ direction: z.ZodEnum<typeof ScoreDirection>;
251
+ points: z.ZodNumber;
252
+ }, z.core.$strip>, z.ZodObject<{
253
+ type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
254
+ rotations: z.ZodNumber;
255
+ }, z.core.$strip>, z.ZodObject<{
256
+ type: z.ZodLiteral<PinchConditionType.FROM_SET>;
257
+ set: z.ZodNumber;
258
+ }, z.core.$strip>, z.ZodObject<{
259
+ type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
260
+ score: z.ZodNumber;
261
+ }, z.core.$strip>, z.ZodObject<{
262
+ type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
263
+ score: z.ZodNumber;
264
+ }, z.core.$strip>, z.ZodObject<{
265
+ type: z.ZodLiteral<PinchConditionType.PHASE>;
266
+ phase: z.ZodEnum<typeof MatchPhase>;
267
+ }, z.core.$strip>], "type">>>;
268
+ subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
160
269
  starter: z.ZodCustom<Player, Player>;
161
270
  }, z.core.$strip>;
@@ -20,6 +20,7 @@ export const PinchConditionSchema = z.discriminatedUnion('type', [
20
20
  ]);
21
21
  export const ConditionLogicSchema = z.union([z.literal('ALL'), z.literal('ANY')]);
22
22
  export const SubModeSchema = z.enum(['FATIGUE', 'PINCH', 'NEVER']);
23
+ export const SubBackModeSchema = z.enum(['FATIGUE', 'CONDITIONS']);
23
24
  // Fields shared by the all-sets config and each per-set config.
24
25
  const setSubConfigShape = {
25
26
  mode: SubModeSchema,
@@ -27,9 +28,16 @@ const setSubConfigShape = {
27
28
  fatigueBand: z.nativeEnum(EnergyBand).optional(),
28
29
  benchFatigueBand: z.nativeEnum(EnergyBand).optional(),
29
30
  conditions: z.array(PinchConditionSchema).optional(),
30
- conditionLogic: ConditionLogicSchema.optional()
31
+ conditionLogic: ConditionLogicSchema.optional(),
32
+ // pinch ("Conditions") mode. true = serve-and-return pinch server; false/absent = regular conditional sub.
33
+ pinchServer: z.boolean().optional(),
34
+ // a regular conditional sub's sub-back trigger + its condition set (subBackMode === 'CONDITIONS').
35
+ subBackMode: SubBackModeSchema.optional(),
36
+ subBackConditions: z.array(PinchConditionSchema).optional(),
37
+ subBackConditionLogic: ConditionLogicSchema.optional()
31
38
  };
32
- // Pinch mode needs a designated bench player (the server) and at least one condition.
39
+ // Pinch ("Conditions") mode needs a designated bench player and at least one sub-in condition. A regular
40
+ // conditional sub (pinchServer !== true) with a CONDITIONS sub-back also needs at least one sub-back condition.
33
41
  function refinePinch(data, ctx) {
34
42
  if (data.mode !== 'PINCH')
35
43
  return;
@@ -39,6 +47,10 @@ function refinePinch(data, ctx) {
39
47
  if (data.conditions == null || data.conditions.length === 0) {
40
48
  ctx.addIssue({ code: 'custom', message: 'PINCH_SERVER_REQUIRES_CONDITIONS', path: ['conditions'] });
41
49
  }
50
+ if (data.pinchServer !== true && data.subBackMode === 'CONDITIONS' &&
51
+ (data.subBackConditions == null || data.subBackConditions.length === 0)) {
52
+ ctx.addIssue({ code: 'custom', message: 'CONDITIONAL_SUB_BACK_REQUIRES_CONDITIONS', path: ['subBackConditions'] });
53
+ }
42
54
  }
43
55
  export const SetSubConfigSchema = z.object(setSubConfigShape).superRefine(refinePinch);
44
56
  export const DesignatedSubSchema = z.object({
@@ -216,4 +216,77 @@ describe('DesignatedSubSchema', () => {
216
216
  });
217
217
  expect(res.success).toBe(false);
218
218
  });
219
+ it('accepts a pinch-mode entry marked as a pinch server (serve-and-return)', () => {
220
+ const res = DesignatedSubSchema.safeParse({
221
+ starter,
222
+ bench,
223
+ mode: 'PINCH',
224
+ conditions: [{ type: PinchConditionType.ASAP }],
225
+ pinchServer: true
226
+ });
227
+ expect(res.success).toBe(true);
228
+ });
229
+ it('accepts a regular conditional sub (pinchServer false) with a tiredness sub-back', () => {
230
+ const res = DesignatedSubSchema.safeParse({
231
+ starter,
232
+ bench,
233
+ mode: 'PINCH',
234
+ conditions: [{ type: PinchConditionType.SCORE_DIFF, direction: ScoreDirection.TRAILING, points: 3 }],
235
+ pinchServer: false,
236
+ subBackMode: 'FATIGUE',
237
+ benchFatigueBand: EnergyBand.TIRED
238
+ });
239
+ expect(res.success).toBe(true);
240
+ });
241
+ it('accepts a regular conditional sub with a condition-based sub-back', () => {
242
+ const res = DesignatedSubSchema.safeParse({
243
+ starter,
244
+ bench,
245
+ mode: 'PINCH',
246
+ conditions: [{ type: PinchConditionType.SCORE_DIFF, direction: ScoreDirection.TRAILING, points: 3 }],
247
+ pinchServer: false,
248
+ subBackMode: 'CONDITIONS',
249
+ subBackConditions: [{ type: PinchConditionType.TEAM_SET_POINT, margin: 2, opponent: OpponentRelation.EITHER }],
250
+ subBackConditionLogic: 'ANY'
251
+ });
252
+ expect(res.success).toBe(true);
253
+ });
254
+ it('rejects a condition-based sub-back with no sub-back conditions', () => {
255
+ const res = DesignatedSubSchema.safeParse({
256
+ starter,
257
+ bench,
258
+ mode: 'PINCH',
259
+ conditions: [{ type: PinchConditionType.ASAP }],
260
+ pinchServer: false,
261
+ subBackMode: 'CONDITIONS',
262
+ subBackConditions: []
263
+ });
264
+ expect(res.success).toBe(false);
265
+ if (!res.success) {
266
+ expect(res.error.issues.some(issue => issue.message === 'CONDITIONAL_SUB_BACK_REQUIRES_CONDITIONS')).toBe(true);
267
+ }
268
+ });
269
+ it('does not require sub-back conditions for a pinch server even under subBackMode CONDITIONS', () => {
270
+ // A pinch server never uses a sub-back rule (it serve-reverts), so the condition requirement is skipped.
271
+ const res = DesignatedSubSchema.safeParse({
272
+ starter,
273
+ bench,
274
+ mode: 'PINCH',
275
+ conditions: [{ type: PinchConditionType.ASAP }],
276
+ pinchServer: true,
277
+ subBackMode: 'CONDITIONS'
278
+ });
279
+ expect(res.success).toBe(true);
280
+ });
281
+ it('rejects an invalid subBackMode', () => {
282
+ const res = DesignatedSubSchema.safeParse({
283
+ starter,
284
+ bench,
285
+ mode: 'PINCH',
286
+ conditions: [{ type: PinchConditionType.ASAP }],
287
+ pinchServer: false,
288
+ subBackMode: 'WHENEVER'
289
+ });
290
+ expect(res.success).toBe(false);
291
+ });
219
292
  });
@@ -3,15 +3,15 @@ import { EnergyBand } from '../energy-band';
3
3
  export declare const LiberoSubModeSchema: z.ZodEnum<{
4
4
  NEVER: "NEVER";
5
5
  FATIGUE: "FATIGUE";
6
- ALWAYS: "ALWAYS";
7
6
  CONDITIONS: "CONDITIONS";
7
+ ALWAYS: "ALWAYS";
8
8
  }>;
9
9
  export declare const LiberoSetSubConfigSchema: z.ZodObject<{
10
10
  mode: z.ZodEnum<{
11
11
  NEVER: "NEVER";
12
12
  FATIGUE: "FATIGUE";
13
- ALWAYS: "ALWAYS";
14
13
  CONDITIONS: "CONDITIONS";
14
+ ALWAYS: "ALWAYS";
15
15
  }>;
16
16
  fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
17
17
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -50,8 +50,8 @@ export declare const LiberoSubConfigSchema: z.ZodObject<{
50
50
  mode: z.ZodEnum<{
51
51
  NEVER: "NEVER";
52
52
  FATIGUE: "FATIGUE";
53
- ALWAYS: "ALWAYS";
54
53
  CONDITIONS: "CONDITIONS";
54
+ ALWAYS: "ALWAYS";
55
55
  }>;
56
56
  fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
57
57
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -88,8 +88,8 @@ export declare const LiberoSubConfigSchema: z.ZodObject<{
88
88
  mode: z.ZodEnum<{
89
89
  NEVER: "NEVER";
90
90
  FATIGUE: "FATIGUE";
91
- ALWAYS: "ALWAYS";
92
91
  CONDITIONS: "CONDITIONS";
92
+ ALWAYS: "ALWAYS";
93
93
  }>;
94
94
  fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
95
95
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{