volleyballsimtypes 0.0.477 → 0.0.479
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.
- package/dist/cjs/src/service/index.d.ts +1 -0
- package/dist/cjs/src/service/index.js +1 -0
- package/dist/cjs/src/service/notification/catalog/en.json +41 -0
- package/dist/cjs/src/service/notification/catalog/es.json +41 -0
- package/dist/cjs/src/service/notification/catalog/fr.json +41 -0
- package/dist/cjs/src/service/notification/catalog/index.d.ts +3 -0
- package/dist/cjs/src/service/notification/catalog/index.js +22 -0
- package/dist/cjs/src/service/notification/catalog/it.json +41 -0
- package/dist/cjs/src/service/notification/catalog/pl.json +41 -0
- package/dist/cjs/src/service/notification/catalog/pt-BR.json +41 -0
- package/dist/cjs/src/service/notification/catalog/tr.json +41 -0
- package/dist/cjs/src/service/notification/index.d.ts +5 -0
- package/dist/cjs/src/service/notification/index.js +23 -0
- package/dist/cjs/src/service/notification/notification-type.d.ts +6 -0
- package/dist/cjs/src/service/notification/notification-type.js +30 -0
- package/dist/cjs/src/service/notification/notification.test.d.ts +1 -0
- package/dist/cjs/src/service/notification/notification.test.js +128 -0
- package/dist/cjs/src/service/notification/payloads.d.ts +34 -0
- package/dist/cjs/src/service/notification/payloads.js +5 -0
- package/dist/cjs/src/service/notification/registry.d.ts +13 -0
- package/dist/cjs/src/service/notification/registry.js +112 -0
- package/dist/cjs/src/service/notification/render.d.ts +10 -0
- package/dist/cjs/src/service/notification/render.js +64 -0
- package/dist/cjs/src/service/team/pinch-condition.d.ts +7 -0
- package/dist/cjs/src/service/team/pinch-condition.js +10 -1
- package/dist/cjs/src/service/team/schemas/designated-sub.z.d.ts +22 -1
- package/dist/cjs/src/service/team/schemas/designated-sub.z.js +5 -3
- package/dist/cjs/src/service/team/schemas/designated-sub.z.test.js +10 -0
- package/dist/cjs/src/service/team/schemas/libero-sub.z.d.ts +9 -0
- package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +18 -0
- package/dist/cjs/src/service/team/schemas/team.z.d.ts +18 -0
- package/dist/esm/src/service/index.d.ts +1 -0
- package/dist/esm/src/service/index.js +1 -0
- package/dist/esm/src/service/notification/catalog/en.json +41 -0
- package/dist/esm/src/service/notification/catalog/es.json +41 -0
- package/dist/esm/src/service/notification/catalog/fr.json +41 -0
- package/dist/esm/src/service/notification/catalog/index.d.ts +3 -0
- package/dist/esm/src/service/notification/catalog/index.js +16 -0
- package/dist/esm/src/service/notification/catalog/it.json +41 -0
- package/dist/esm/src/service/notification/catalog/pl.json +41 -0
- package/dist/esm/src/service/notification/catalog/pt-BR.json +41 -0
- package/dist/esm/src/service/notification/catalog/tr.json +41 -0
- package/dist/esm/src/service/notification/index.d.ts +5 -0
- package/dist/esm/src/service/notification/index.js +5 -0
- package/dist/esm/src/service/notification/notification-type.d.ts +6 -0
- package/dist/esm/src/service/notification/notification-type.js +26 -0
- package/dist/esm/src/service/notification/notification.test.d.ts +1 -0
- package/dist/esm/src/service/notification/notification.test.js +126 -0
- package/dist/esm/src/service/notification/payloads.d.ts +34 -0
- package/dist/esm/src/service/notification/payloads.js +4 -0
- package/dist/esm/src/service/notification/registry.d.ts +13 -0
- package/dist/esm/src/service/notification/registry.js +109 -0
- package/dist/esm/src/service/notification/render.d.ts +10 -0
- package/dist/esm/src/service/notification/render.js +58 -0
- package/dist/esm/src/service/team/pinch-condition.d.ts +7 -0
- package/dist/esm/src/service/team/pinch-condition.js +9 -0
- package/dist/esm/src/service/team/schemas/designated-sub.z.d.ts +22 -1
- package/dist/esm/src/service/team/schemas/designated-sub.z.js +6 -4
- package/dist/esm/src/service/team/schemas/designated-sub.z.test.js +11 -1
- package/dist/esm/src/service/team/schemas/libero-sub.z.d.ts +9 -0
- package/dist/esm/src/service/team/schemas/tactics.z.d.ts +18 -0
- package/dist/esm/src/service/team/schemas/team.z.d.ts +18 -0
- package/package.json +6 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface MatchResultTeam {
|
|
2
|
+
id?: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface MatchResultPayload {
|
|
6
|
+
matchId?: string;
|
|
7
|
+
homeTeam?: MatchResultTeam;
|
|
8
|
+
awayTeam?: MatchResultTeam;
|
|
9
|
+
result?: 'WIN' | 'LOSS';
|
|
10
|
+
side?: 'HOME' | 'AWAY';
|
|
11
|
+
}
|
|
12
|
+
export interface InjuryPayload {
|
|
13
|
+
playerId?: string;
|
|
14
|
+
playerName?: string;
|
|
15
|
+
playerNickname?: string;
|
|
16
|
+
teamId?: string;
|
|
17
|
+
matchId?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface InjuryRecoveredPayload {
|
|
20
|
+
playerId?: string;
|
|
21
|
+
playerName?: string;
|
|
22
|
+
playerNickname?: string;
|
|
23
|
+
teamId?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface SeasonEndPayload {
|
|
26
|
+
competitionId?: string;
|
|
27
|
+
leagueId?: string;
|
|
28
|
+
position?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface PromotionPayload {
|
|
31
|
+
teamId?: string;
|
|
32
|
+
amount?: number;
|
|
33
|
+
}
|
|
34
|
+
export type NotificationPayload = MatchResultPayload | InjuryPayload | InjuryRecoveredPayload | SeasonEndPayload | PromotionPayload | Record<string, unknown>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Typed payload shapes per notification type, for call-site safety in the Sim/API. The DB `Notification.payload`
|
|
2
|
+
// column is jsonb (free-form), so the registry reads payloads defensively (Record<string, unknown>); these
|
|
3
|
+
// interfaces document the intended shape and can be used by emitters when constructing the payload.
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type NotificationType } from './notification-type';
|
|
2
|
+
export type CatalogKeyResolver = string | ((payload: Record<string, unknown>) => string);
|
|
3
|
+
export interface NotificationDescriptor {
|
|
4
|
+
emitsPush: boolean;
|
|
5
|
+
deepLink?: (payload: Record<string, unknown>) => string;
|
|
6
|
+
pushTitleKey?: CatalogKeyResolver;
|
|
7
|
+
pushBodyKey?: CatalogKeyResolver;
|
|
8
|
+
inAppMessageKey?: CatalogKeyResolver;
|
|
9
|
+
richRender?: NotificationType;
|
|
10
|
+
params?: (payload: Record<string, unknown>) => Record<string, string>;
|
|
11
|
+
catalogKeys: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare const NOTIFICATION_REGISTRY: Record<NotificationType, NotificationDescriptor>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
function readString(value) {
|
|
2
|
+
return typeof value === 'string' && value !== '' ? value : null;
|
|
3
|
+
}
|
|
4
|
+
function teamPath(payload) {
|
|
5
|
+
const teamId = readString(payload.teamId);
|
|
6
|
+
return teamId != null ? `/team/${encodeURIComponent(teamId)}` : '/';
|
|
7
|
+
}
|
|
8
|
+
// MATCH_RESULT copy is recipient-relative: `result` is WIN/LOSS from the recipient's view and `side` marks
|
|
9
|
+
// which team is theirs, so the OTHER team is the opponent. When side/names are missing, do not name a winner.
|
|
10
|
+
function matchOpponent(payload) {
|
|
11
|
+
const homeTeam = typeof payload.homeTeam === 'object' && payload.homeTeam != null ? payload.homeTeam : {};
|
|
12
|
+
const awayTeam = typeof payload.awayTeam === 'object' && payload.awayTeam != null ? payload.awayTeam : {};
|
|
13
|
+
const homeName = readString(homeTeam.name);
|
|
14
|
+
const awayName = readString(awayTeam.name);
|
|
15
|
+
if (payload.side === 'HOME')
|
|
16
|
+
return awayName;
|
|
17
|
+
if (payload.side === 'AWAY')
|
|
18
|
+
return homeName;
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function injuredName(payload) {
|
|
22
|
+
// Matches the old Sim injuredPlayerName fallback so a name-less payload never renders a subjectless sentence.
|
|
23
|
+
return readString(payload.playerNickname) ?? readString(payload.playerName) ?? 'A player';
|
|
24
|
+
}
|
|
25
|
+
export const NOTIFICATION_REGISTRY = {
|
|
26
|
+
MATCH_RESULT: {
|
|
27
|
+
emitsPush: true,
|
|
28
|
+
deepLink: (p) => {
|
|
29
|
+
const matchId = readString(p.matchId);
|
|
30
|
+
return matchId != null ? `/match/${encodeURIComponent(matchId)}` : '/';
|
|
31
|
+
},
|
|
32
|
+
pushTitleKey: 'matchResult.title',
|
|
33
|
+
pushBodyKey: (p) => {
|
|
34
|
+
const outcome = p.result === 'WIN' ? 'won' : 'lost';
|
|
35
|
+
const named = matchOpponent(p) != null ? 'Named' : 'Unnamed';
|
|
36
|
+
return `matchResult.${outcome}${named}`;
|
|
37
|
+
},
|
|
38
|
+
params: (p) => ({ opponent: matchOpponent(p) ?? '' }),
|
|
39
|
+
richRender: 'MATCH_RESULT',
|
|
40
|
+
catalogKeys: ['matchResult.title', 'matchResult.wonNamed', 'matchResult.wonUnnamed', 'matchResult.lostNamed', 'matchResult.lostUnnamed']
|
|
41
|
+
},
|
|
42
|
+
INJURY: {
|
|
43
|
+
emitsPush: true,
|
|
44
|
+
deepLink: () => '/infirmary',
|
|
45
|
+
pushTitleKey: 'injury.title',
|
|
46
|
+
pushBodyKey: 'injury.body',
|
|
47
|
+
inAppMessageKey: 'injury.body',
|
|
48
|
+
params: (p) => ({ name: injuredName(p) }),
|
|
49
|
+
catalogKeys: ['injury.title', 'injury.body']
|
|
50
|
+
},
|
|
51
|
+
INJURY_RECOVERED: {
|
|
52
|
+
emitsPush: true,
|
|
53
|
+
deepLink: teamPath,
|
|
54
|
+
pushTitleKey: 'injuryRecovered.title',
|
|
55
|
+
pushBodyKey: 'injuryRecovered.body',
|
|
56
|
+
inAppMessageKey: 'injuryRecovered.body',
|
|
57
|
+
params: (p) => ({ name: injuredName(p) }),
|
|
58
|
+
catalogKeys: ['injuryRecovered.title', 'injuryRecovered.body']
|
|
59
|
+
},
|
|
60
|
+
SEASON_END: {
|
|
61
|
+
emitsPush: true,
|
|
62
|
+
deepLink: (p) => {
|
|
63
|
+
const leagueId = readString(p.leagueId);
|
|
64
|
+
return leagueId != null ? `/league/${encodeURIComponent(leagueId)}` : '/league';
|
|
65
|
+
},
|
|
66
|
+
pushTitleKey: 'seasonEnd.title',
|
|
67
|
+
pushBodyKey: 'seasonEnd.body',
|
|
68
|
+
inAppMessageKey: 'seasonEnd.body',
|
|
69
|
+
catalogKeys: ['seasonEnd.title', 'seasonEnd.body']
|
|
70
|
+
},
|
|
71
|
+
GRANT_RECEIVED: {
|
|
72
|
+
emitsPush: true,
|
|
73
|
+
deepLink: () => '/recruit',
|
|
74
|
+
pushTitleKey: 'grant.title',
|
|
75
|
+
pushBodyKey: 'grant.body',
|
|
76
|
+
inAppMessageKey: 'grant.body',
|
|
77
|
+
catalogKeys: ['grant.title', 'grant.body']
|
|
78
|
+
},
|
|
79
|
+
PROMOTION: {
|
|
80
|
+
emitsPush: true,
|
|
81
|
+
deepLink: teamPath,
|
|
82
|
+
pushTitleKey: 'promotion.title',
|
|
83
|
+
pushBodyKey: 'promotion.body',
|
|
84
|
+
inAppMessageKey: 'promotion.body',
|
|
85
|
+
catalogKeys: ['promotion.title', 'promotion.body']
|
|
86
|
+
},
|
|
87
|
+
DAILY_LOGIN: {
|
|
88
|
+
emitsPush: true,
|
|
89
|
+
deepLink: () => '/recruit',
|
|
90
|
+
pushTitleKey: 'dailyLogin.title',
|
|
91
|
+
pushBodyKey: 'dailyLogin.body',
|
|
92
|
+
inAppMessageKey: 'dailyLogin.body',
|
|
93
|
+
catalogKeys: ['dailyLogin.title', 'dailyLogin.body']
|
|
94
|
+
},
|
|
95
|
+
WELCOME: {
|
|
96
|
+
emitsPush: true,
|
|
97
|
+
deepLink: () => '/recruit',
|
|
98
|
+
pushTitleKey: 'welcome.title',
|
|
99
|
+
pushBodyKey: 'welcome.body',
|
|
100
|
+
richRender: 'WELCOME',
|
|
101
|
+
catalogKeys: ['welcome.title', 'welcome.body']
|
|
102
|
+
},
|
|
103
|
+
// In-app only (no push); the UI keeps a bespoke renderer (amount formatting) and its own `compensation` label.
|
|
104
|
+
COMPENSATION: {
|
|
105
|
+
emitsPush: false,
|
|
106
|
+
richRender: 'COMPENSATION',
|
|
107
|
+
catalogKeys: []
|
|
108
|
+
}
|
|
109
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type NotificationLocale } from './notification-type';
|
|
2
|
+
export interface RenderedPush {
|
|
3
|
+
title: string;
|
|
4
|
+
body: string;
|
|
5
|
+
data: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function interpolate(template: string, params: Record<string, string>): string;
|
|
8
|
+
export declare function translateNotification(locale: NotificationLocale, key: string, params?: Record<string, string>): string;
|
|
9
|
+
export declare function renderPush(type: string, payload: Record<string, unknown>, locale: NotificationLocale): RenderedPush | undefined;
|
|
10
|
+
export declare function notificationPath(type: string, payload: Record<string, unknown>): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DEFAULT_NOTIFICATION_LOCALE } from './notification-type';
|
|
2
|
+
import { NOTIFICATION_REGISTRY } from './registry';
|
|
3
|
+
import { NOTIFICATION_CATALOG } from './catalog';
|
|
4
|
+
function readNested(node, dotKey) {
|
|
5
|
+
let current = node;
|
|
6
|
+
for (const part of dotKey.split('.')) {
|
|
7
|
+
if (typeof current !== 'object' || current == null)
|
|
8
|
+
return undefined;
|
|
9
|
+
current = current[part];
|
|
10
|
+
}
|
|
11
|
+
return typeof current === 'string' ? current : undefined;
|
|
12
|
+
}
|
|
13
|
+
function lookupCatalog(locale, dotKey) {
|
|
14
|
+
return readNested(NOTIFICATION_CATALOG[locale], dotKey) ??
|
|
15
|
+
readNested(NOTIFICATION_CATALOG[DEFAULT_NOTIFICATION_LOCALE], dotKey) ??
|
|
16
|
+
dotKey;
|
|
17
|
+
}
|
|
18
|
+
function resolveKey(resolver, payload) {
|
|
19
|
+
if (resolver == null)
|
|
20
|
+
return undefined;
|
|
21
|
+
return typeof resolver === 'function' ? resolver(payload) : resolver;
|
|
22
|
+
}
|
|
23
|
+
export function interpolate(template, params) {
|
|
24
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => params[key] ?? '');
|
|
25
|
+
}
|
|
26
|
+
// Resolve one catalog key to a localized, interpolated string. Missing key/locale falls back to English,
|
|
27
|
+
// then to the raw key. Exposed so consumers can render notification copy off the shared catalog too.
|
|
28
|
+
export function translateNotification(locale, key, params = {}) {
|
|
29
|
+
return interpolate(lookupCatalog(locale, key), params);
|
|
30
|
+
}
|
|
31
|
+
// Build the localized push payload for a notification. Returns undefined for in-app-only types (emitsPush
|
|
32
|
+
// false, e.g. COMPENSATION). Unknown/future types fall back to the generic copy and the root path, matching
|
|
33
|
+
// the previous hardcoded fallback. Callers pass the recipient's locale (normalizeNotificationLocale first).
|
|
34
|
+
export function renderPush(type, payload, locale) {
|
|
35
|
+
const buildData = (path) => ({ type, payload: JSON.stringify(payload), path });
|
|
36
|
+
const descriptor = NOTIFICATION_REGISTRY[type];
|
|
37
|
+
if (descriptor == null) {
|
|
38
|
+
return {
|
|
39
|
+
title: translateNotification(locale, 'generic.title'),
|
|
40
|
+
body: translateNotification(locale, 'generic.body'),
|
|
41
|
+
data: buildData('/')
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (!descriptor.emitsPush)
|
|
45
|
+
return undefined;
|
|
46
|
+
const params = descriptor.params?.(payload) ?? {};
|
|
47
|
+
const titleKey = resolveKey(descriptor.pushTitleKey, payload) ?? 'generic.title';
|
|
48
|
+
const bodyKey = resolveKey(descriptor.pushBodyKey, payload) ?? 'generic.body';
|
|
49
|
+
return {
|
|
50
|
+
title: translateNotification(locale, titleKey, params),
|
|
51
|
+
body: translateNotification(locale, bodyKey, params),
|
|
52
|
+
data: buildData(descriptor.deepLink?.(payload) ?? '/')
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Deep-link path for a type/payload, used for both the push data.path and the in-app click target.
|
|
56
|
+
export function notificationPath(type, payload) {
|
|
57
|
+
return NOTIFICATION_REGISTRY[type]?.deepLink?.(payload) ?? '/';
|
|
58
|
+
}
|
|
@@ -22,6 +22,10 @@ export declare enum ScoreDirection {
|
|
|
22
22
|
TRAILING = "TRAILING",
|
|
23
23
|
LEADING = "LEADING"
|
|
24
24
|
}
|
|
25
|
+
export declare enum Comparison {
|
|
26
|
+
AT_LEAST = "AT_LEAST",
|
|
27
|
+
AT_MOST = "AT_MOST"
|
|
28
|
+
}
|
|
25
29
|
export interface AsapCondition {
|
|
26
30
|
readonly type: PinchConditionType.ASAP;
|
|
27
31
|
}
|
|
@@ -38,6 +42,7 @@ export interface ScoreDiffCondition {
|
|
|
38
42
|
readonly type: PinchConditionType.SCORE_DIFF;
|
|
39
43
|
readonly direction: ScoreDirection;
|
|
40
44
|
readonly points: number;
|
|
45
|
+
readonly comparison?: Comparison;
|
|
41
46
|
}
|
|
42
47
|
export interface AfterRotationsCondition {
|
|
43
48
|
readonly type: PinchConditionType.AFTER_ROTATIONS;
|
|
@@ -50,10 +55,12 @@ export interface FromSetCondition {
|
|
|
50
55
|
export interface MinOwnScoreCondition {
|
|
51
56
|
readonly type: PinchConditionType.MIN_OWN_SCORE;
|
|
52
57
|
readonly score: number;
|
|
58
|
+
readonly comparison?: Comparison;
|
|
53
59
|
}
|
|
54
60
|
export interface MinOpponentScoreCondition {
|
|
55
61
|
readonly type: PinchConditionType.MIN_OPPONENT_SCORE;
|
|
56
62
|
readonly score: number;
|
|
63
|
+
readonly comparison?: Comparison;
|
|
57
64
|
}
|
|
58
65
|
export interface PhaseCondition {
|
|
59
66
|
readonly type: PinchConditionType.PHASE;
|
|
@@ -31,3 +31,12 @@ export var ScoreDirection;
|
|
|
31
31
|
ScoreDirection["TRAILING"] = "TRAILING";
|
|
32
32
|
ScoreDirection["LEADING"] = "LEADING";
|
|
33
33
|
})(ScoreDirection || (ScoreDirection = {}));
|
|
34
|
+
// Comparison direction for a numeric-threshold condition (SCORE_DIFF / MIN_OWN_SCORE / MIN_OPPONENT_SCORE).
|
|
35
|
+
// AT_LEAST ("Or more", value >= threshold) is the default when absent, so every existing stored condition keeps
|
|
36
|
+
// its `>=` behavior. AT_MOST ("Or less", value <= threshold) lets a rule fire as a value SHRINKS (e.g. a sub-back
|
|
37
|
+
// once "Leading by 2 or less" holds, instead of the always-implied "Leading by 2 or more").
|
|
38
|
+
export var Comparison;
|
|
39
|
+
(function (Comparison) {
|
|
40
|
+
Comparison["AT_LEAST"] = "AT_LEAST";
|
|
41
|
+
Comparison["AT_MOST"] = "AT_MOST";
|
|
42
|
+
})(Comparison || (Comparison = {}));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { Player } from '../../player';
|
|
3
3
|
import { EnergyBand } from '../energy-band';
|
|
4
|
-
import { MatchPhase, OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
|
|
4
|
+
import { Comparison, MatchPhase, OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
|
|
5
5
|
export declare const SubBandSchema: z.ZodUnion<readonly [z.ZodEnum<typeof EnergyBand>, z.ZodLiteral<"NEVER">]>;
|
|
6
6
|
export declare const PinchConditionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
7
7
|
type: z.ZodLiteral<PinchConditionType.ASAP>;
|
|
@@ -16,6 +16,7 @@ export declare const PinchConditionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
16
16
|
type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
|
|
17
17
|
direction: z.ZodEnum<typeof ScoreDirection>;
|
|
18
18
|
points: z.ZodNumber;
|
|
19
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
19
20
|
}, z.core.$strip>, z.ZodObject<{
|
|
20
21
|
type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
|
|
21
22
|
rotations: z.ZodNumber;
|
|
@@ -25,9 +26,11 @@ export declare const PinchConditionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
|
|
|
25
26
|
}, z.core.$strip>, z.ZodObject<{
|
|
26
27
|
type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
|
|
27
28
|
score: z.ZodNumber;
|
|
29
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
28
30
|
}, z.core.$strip>, z.ZodObject<{
|
|
29
31
|
type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
30
32
|
score: z.ZodNumber;
|
|
33
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
31
34
|
}, z.core.$strip>, z.ZodObject<{
|
|
32
35
|
type: z.ZodLiteral<PinchConditionType.PHASE>;
|
|
33
36
|
phase: z.ZodEnum<typeof MatchPhase>;
|
|
@@ -64,6 +67,7 @@ export declare const SetSubConfigSchema: z.ZodObject<{
|
|
|
64
67
|
type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
|
|
65
68
|
direction: z.ZodEnum<typeof ScoreDirection>;
|
|
66
69
|
points: z.ZodNumber;
|
|
70
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
67
71
|
}, z.core.$strip>, z.ZodObject<{
|
|
68
72
|
type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
|
|
69
73
|
rotations: z.ZodNumber;
|
|
@@ -73,9 +77,11 @@ export declare const SetSubConfigSchema: z.ZodObject<{
|
|
|
73
77
|
}, z.core.$strip>, z.ZodObject<{
|
|
74
78
|
type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
|
|
75
79
|
score: z.ZodNumber;
|
|
80
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
76
81
|
}, z.core.$strip>, z.ZodObject<{
|
|
77
82
|
type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
78
83
|
score: z.ZodNumber;
|
|
84
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
79
85
|
}, z.core.$strip>, z.ZodObject<{
|
|
80
86
|
type: z.ZodLiteral<PinchConditionType.PHASE>;
|
|
81
87
|
phase: z.ZodEnum<typeof MatchPhase>;
|
|
@@ -99,6 +105,7 @@ export declare const SetSubConfigSchema: z.ZodObject<{
|
|
|
99
105
|
type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
|
|
100
106
|
direction: z.ZodEnum<typeof ScoreDirection>;
|
|
101
107
|
points: z.ZodNumber;
|
|
108
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
102
109
|
}, z.core.$strip>, z.ZodObject<{
|
|
103
110
|
type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
|
|
104
111
|
rotations: z.ZodNumber;
|
|
@@ -108,9 +115,11 @@ export declare const SetSubConfigSchema: z.ZodObject<{
|
|
|
108
115
|
}, z.core.$strip>, z.ZodObject<{
|
|
109
116
|
type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
|
|
110
117
|
score: z.ZodNumber;
|
|
118
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
111
119
|
}, z.core.$strip>, z.ZodObject<{
|
|
112
120
|
type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
113
121
|
score: z.ZodNumber;
|
|
122
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
114
123
|
}, z.core.$strip>, z.ZodObject<{
|
|
115
124
|
type: z.ZodLiteral<PinchConditionType.PHASE>;
|
|
116
125
|
phase: z.ZodEnum<typeof MatchPhase>;
|
|
@@ -140,6 +149,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
140
149
|
type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
|
|
141
150
|
direction: z.ZodEnum<typeof ScoreDirection>;
|
|
142
151
|
points: z.ZodNumber;
|
|
152
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
143
153
|
}, z.core.$strip>, z.ZodObject<{
|
|
144
154
|
type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
|
|
145
155
|
rotations: z.ZodNumber;
|
|
@@ -149,9 +159,11 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
149
159
|
}, z.core.$strip>, z.ZodObject<{
|
|
150
160
|
type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
|
|
151
161
|
score: z.ZodNumber;
|
|
162
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
152
163
|
}, z.core.$strip>, z.ZodObject<{
|
|
153
164
|
type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
154
165
|
score: z.ZodNumber;
|
|
166
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
155
167
|
}, z.core.$strip>, z.ZodObject<{
|
|
156
168
|
type: z.ZodLiteral<PinchConditionType.PHASE>;
|
|
157
169
|
phase: z.ZodEnum<typeof MatchPhase>;
|
|
@@ -175,6 +187,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
175
187
|
type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
|
|
176
188
|
direction: z.ZodEnum<typeof ScoreDirection>;
|
|
177
189
|
points: z.ZodNumber;
|
|
190
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
178
191
|
}, z.core.$strip>, z.ZodObject<{
|
|
179
192
|
type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
|
|
180
193
|
rotations: z.ZodNumber;
|
|
@@ -184,9 +197,11 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
184
197
|
}, z.core.$strip>, z.ZodObject<{
|
|
185
198
|
type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
|
|
186
199
|
score: z.ZodNumber;
|
|
200
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
187
201
|
}, z.core.$strip>, z.ZodObject<{
|
|
188
202
|
type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
189
203
|
score: z.ZodNumber;
|
|
204
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
190
205
|
}, z.core.$strip>, z.ZodObject<{
|
|
191
206
|
type: z.ZodLiteral<PinchConditionType.PHASE>;
|
|
192
207
|
phase: z.ZodEnum<typeof MatchPhase>;
|
|
@@ -214,6 +229,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
214
229
|
type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
|
|
215
230
|
direction: z.ZodEnum<typeof ScoreDirection>;
|
|
216
231
|
points: z.ZodNumber;
|
|
232
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
217
233
|
}, z.core.$strip>, z.ZodObject<{
|
|
218
234
|
type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
|
|
219
235
|
rotations: z.ZodNumber;
|
|
@@ -223,9 +239,11 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
223
239
|
}, z.core.$strip>, z.ZodObject<{
|
|
224
240
|
type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
|
|
225
241
|
score: z.ZodNumber;
|
|
242
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
226
243
|
}, z.core.$strip>, z.ZodObject<{
|
|
227
244
|
type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
228
245
|
score: z.ZodNumber;
|
|
246
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
229
247
|
}, z.core.$strip>, z.ZodObject<{
|
|
230
248
|
type: z.ZodLiteral<PinchConditionType.PHASE>;
|
|
231
249
|
phase: z.ZodEnum<typeof MatchPhase>;
|
|
@@ -249,6 +267,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
249
267
|
type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
|
|
250
268
|
direction: z.ZodEnum<typeof ScoreDirection>;
|
|
251
269
|
points: z.ZodNumber;
|
|
270
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
252
271
|
}, z.core.$strip>, z.ZodObject<{
|
|
253
272
|
type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
|
|
254
273
|
rotations: z.ZodNumber;
|
|
@@ -258,9 +277,11 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
258
277
|
}, z.core.$strip>, z.ZodObject<{
|
|
259
278
|
type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
|
|
260
279
|
score: z.ZodNumber;
|
|
280
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
261
281
|
}, z.core.$strip>, z.ZodObject<{
|
|
262
282
|
type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
263
283
|
score: z.ZodNumber;
|
|
284
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
|
|
264
285
|
}, z.core.$strip>, z.ZodObject<{
|
|
265
286
|
type: z.ZodLiteral<PinchConditionType.PHASE>;
|
|
266
287
|
phase: z.ZodEnum<typeof MatchPhase>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { Player } from '../../player';
|
|
3
3
|
import { EnergyBand } from '../energy-band';
|
|
4
|
-
import { MatchPhase, OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
|
|
4
|
+
import { Comparison, MatchPhase, OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
|
|
5
5
|
const playerInstanceSchema = z.custom((v) => v instanceof Player, {
|
|
6
6
|
message: 'INVALID_PLAYER_INSTANCE'
|
|
7
7
|
});
|
|
@@ -10,11 +10,13 @@ export const PinchConditionSchema = z.discriminatedUnion('type', [
|
|
|
10
10
|
z.object({ type: z.literal(PinchConditionType.ASAP) }),
|
|
11
11
|
z.object({ type: z.literal(PinchConditionType.TEAM_SET_POINT), margin: z.number().int().min(1).max(25), opponent: z.nativeEnum(OpponentRelation) }),
|
|
12
12
|
z.object({ type: z.literal(PinchConditionType.OPPONENT_SET_POINT), margin: z.number().int().min(1).max(25) }),
|
|
13
|
-
|
|
13
|
+
// SCORE_DIFF / MIN_OWN_SCORE / MIN_OPPONENT_SCORE carry an optional comparison direction ("Or more" AT_LEAST,
|
|
14
|
+
// the default; "Or less" AT_MOST). Absent = AT_LEAST so existing conditions keep their `>=` behavior.
|
|
15
|
+
z.object({ type: z.literal(PinchConditionType.SCORE_DIFF), direction: z.nativeEnum(ScoreDirection), points: z.number().int().min(1).max(25), comparison: z.nativeEnum(Comparison).optional() }),
|
|
14
16
|
z.object({ type: z.literal(PinchConditionType.AFTER_ROTATIONS), rotations: z.number().int().min(1).max(6) }),
|
|
15
17
|
z.object({ type: z.literal(PinchConditionType.FROM_SET), set: z.number().int().min(1).max(5) }),
|
|
16
|
-
z.object({ type: z.literal(PinchConditionType.MIN_OWN_SCORE), score: z.number().int().min(0).max(40) }),
|
|
17
|
-
z.object({ type: z.literal(PinchConditionType.MIN_OPPONENT_SCORE), score: z.number().int().min(0).max(40) }),
|
|
18
|
+
z.object({ type: z.literal(PinchConditionType.MIN_OWN_SCORE), score: z.number().int().min(0).max(40), comparison: z.nativeEnum(Comparison).optional() }),
|
|
19
|
+
z.object({ type: z.literal(PinchConditionType.MIN_OPPONENT_SCORE), score: z.number().int().min(0).max(40), comparison: z.nativeEnum(Comparison).optional() }),
|
|
18
20
|
// PHASE is used by the libero deployment rule (which reuses this schema); pinch servers never offer it.
|
|
19
21
|
z.object({ type: z.literal(PinchConditionType.PHASE), phase: z.nativeEnum(MatchPhase) })
|
|
20
22
|
]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from '@jest/globals';
|
|
2
2
|
import { ConditionLogicSchema, DesignatedSubSchema, PinchConditionSchema, SubBandSchema } from './designated-sub.z';
|
|
3
3
|
import { EnergyBand } from '../energy-band';
|
|
4
|
-
import { OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
|
|
4
|
+
import { Comparison, OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
|
|
5
5
|
import { makeCountry, makePlayer } from '../../test-helpers';
|
|
6
6
|
describe('SubBandSchema', () => {
|
|
7
7
|
it('accepts every energy band and NEVER', () => {
|
|
@@ -57,6 +57,16 @@ describe('PinchConditionSchema', () => {
|
|
|
57
57
|
it('rejects a non-integer numeric field', () => {
|
|
58
58
|
expect(PinchConditionSchema.safeParse({ type: PinchConditionType.FROM_SET, set: 2.5 }).success).toBe(false);
|
|
59
59
|
});
|
|
60
|
+
it('accepts an optional comparison (Or more / Or less) on the numeric-threshold conditions', () => {
|
|
61
|
+
expect(PinchConditionSchema.safeParse({ type: PinchConditionType.SCORE_DIFF, direction: ScoreDirection.LEADING, points: 2, comparison: Comparison.AT_MOST }).success).toBe(true);
|
|
62
|
+
expect(PinchConditionSchema.safeParse({ type: PinchConditionType.MIN_OWN_SCORE, score: 20, comparison: Comparison.AT_MOST }).success).toBe(true);
|
|
63
|
+
expect(PinchConditionSchema.safeParse({ type: PinchConditionType.MIN_OPPONENT_SCORE, score: 20, comparison: Comparison.AT_LEAST }).success).toBe(true);
|
|
64
|
+
// absent comparison stays valid (defaults to AT_LEAST at eval time)
|
|
65
|
+
expect(PinchConditionSchema.safeParse({ type: PinchConditionType.SCORE_DIFF, direction: ScoreDirection.LEADING, points: 2 }).success).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
it('rejects an invalid comparison value', () => {
|
|
68
|
+
expect(PinchConditionSchema.safeParse({ type: PinchConditionType.MIN_OWN_SCORE, score: 20, comparison: 'EXACTLY' }).success).toBe(false);
|
|
69
|
+
});
|
|
60
70
|
});
|
|
61
71
|
describe('DesignatedSubSchema', () => {
|
|
62
72
|
const country = makeCountry();
|
|
@@ -27,6 +27,7 @@ export declare const LiberoSetSubConfigSchema: z.ZodObject<{
|
|
|
27
27
|
type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
|
|
28
28
|
direction: z.ZodEnum<typeof import("..").ScoreDirection>;
|
|
29
29
|
points: z.ZodNumber;
|
|
30
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
|
|
30
31
|
}, z.core.$strip>, z.ZodObject<{
|
|
31
32
|
type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
|
|
32
33
|
rotations: z.ZodNumber;
|
|
@@ -36,9 +37,11 @@ export declare const LiberoSetSubConfigSchema: z.ZodObject<{
|
|
|
36
37
|
}, z.core.$strip>, z.ZodObject<{
|
|
37
38
|
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
|
|
38
39
|
score: z.ZodNumber;
|
|
40
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
|
|
39
41
|
}, z.core.$strip>, z.ZodObject<{
|
|
40
42
|
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
41
43
|
score: z.ZodNumber;
|
|
44
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
|
|
42
45
|
}, z.core.$strip>, z.ZodObject<{
|
|
43
46
|
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
44
47
|
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|
|
@@ -67,6 +70,7 @@ export declare const LiberoSubConfigSchema: z.ZodObject<{
|
|
|
67
70
|
type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
|
|
68
71
|
direction: z.ZodEnum<typeof import("..").ScoreDirection>;
|
|
69
72
|
points: z.ZodNumber;
|
|
73
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
|
|
70
74
|
}, z.core.$strip>, z.ZodObject<{
|
|
71
75
|
type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
|
|
72
76
|
rotations: z.ZodNumber;
|
|
@@ -76,9 +80,11 @@ export declare const LiberoSubConfigSchema: z.ZodObject<{
|
|
|
76
80
|
}, z.core.$strip>, z.ZodObject<{
|
|
77
81
|
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
|
|
78
82
|
score: z.ZodNumber;
|
|
83
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
|
|
79
84
|
}, z.core.$strip>, z.ZodObject<{
|
|
80
85
|
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
81
86
|
score: z.ZodNumber;
|
|
87
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
|
|
82
88
|
}, z.core.$strip>, z.ZodObject<{
|
|
83
89
|
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
84
90
|
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|
|
@@ -105,6 +111,7 @@ export declare const LiberoSubConfigSchema: z.ZodObject<{
|
|
|
105
111
|
type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
|
|
106
112
|
direction: z.ZodEnum<typeof import("..").ScoreDirection>;
|
|
107
113
|
points: z.ZodNumber;
|
|
114
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
|
|
108
115
|
}, z.core.$strip>, z.ZodObject<{
|
|
109
116
|
type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
|
|
110
117
|
rotations: z.ZodNumber;
|
|
@@ -114,9 +121,11 @@ export declare const LiberoSubConfigSchema: z.ZodObject<{
|
|
|
114
121
|
}, z.core.$strip>, z.ZodObject<{
|
|
115
122
|
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
|
|
116
123
|
score: z.ZodNumber;
|
|
124
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
|
|
117
125
|
}, z.core.$strip>, z.ZodObject<{
|
|
118
126
|
type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
|
|
119
127
|
score: z.ZodNumber;
|
|
128
|
+
comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
|
|
120
129
|
}, z.core.$strip>, z.ZodObject<{
|
|
121
130
|
type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
|
|
122
131
|
phase: z.ZodEnum<typeof import("..").MatchPhase>;
|