Skip to content

Tournament bracket · Modules

11 modules · 98 exports · 2 repositories · 35 e2e tests · 3 routes

Who imports who · hover a node
Depends onTournament bracket modulesUsed byTeamPicktournamentAbDivisions2 exportsCheckIn2 exportsDeadline1 exportPickBan16 exportsPreparedMaps8 exportsProgression27 exportsRunningTournaments.serverSeeding2 exportsSkippedRounds8 exportsTournament.server24 exp…Tournament7 exportsActions17 filesRoutes19 filesBrackettournament-bracketComponents18 filesLoaders19 filesOther26 filesRepositories2 filesBracketBuildercalendarShowcaseTournaments.serverStandingstournamentTrophyBackfill.serverUserActivity.serveruser…Routines3 filesScripts2 files

AbDivisions

app/features/tournament-bracket/core/AbDivisions.ts

No overview comment at the top of the file.

Imports
no other modules
Used by
1 action2 routes1 module

Functions & values

function

validate

:13

Division assignments parallel to the seeding for an A/B round robin, or an error message for the organizer: every team needs an A (0) or B (1) assignment, counts may differ by at most 1 (and then only one group is allowed), equal counts must split evenly into groups of even size.

export function validate({
	abDivisionsBySeedOrder,
	groupCount,
}: ValidateArgs): Result<(0 | 1)[], string>
Unit tests (10)
function

countByDivision

:69

Counts checked-in teams by division. Unassigned teams are excluded.

export function countByDivision(teams: { abDivision: number | null }[])
Unit tests (1)

CheckIn

app/features/tournament-bracket/core/CheckIn.ts

No overview comment at the top of the file.

Imports
no other modules
Used by
1 route1 module1 component

Functions & values

function

sharedBracketIdxs

:18

Brackets that share their check-in with the given bracket: every bracket requiring check-in that starts at the same time. A team checked in to one of them is checked in to all, so a team moving between them keeps its check-in.

  • @example

    // Alpha (1) and Beta (2) both start at 18:00, Gamma (3) at 20:00 CheckIn.sharedBracketIdxs(1, progression) // [1, 2]

export function sharedBracketIdxs(
	bracketIdx: number,
	progression: ParsedBracket[],
): number[]
Unit tests (1)
function

isCheckedInToBrackets

:35

Is the team checked in to the brackets (from {@link sharedBracketIdxs})? The latest check-in or check-out among them decides.

export function isCheckedInToBrackets(
	checkIns: CheckInRow[],
	bracketIdxs: number[],
): boolean
Unit tests (1)

Deadline

app/features/tournament-bracket/core/Deadline.ts

No overview comment at the top of the file.

Imports
no other modules
Used by
2 components

Functions & values

function

matchStatus

:46

"normal" if on track, "warning" if behind schedule, "error" if overtime.

export function matchStatus({
	elapsedMinutes,
	gamesCompleted,
	maxGamesCount,
}: {
	elapsedMinutes: number;
	gamesCompleted: number;
	maxGamesCount: number;
}): "normal" | "warning" | "error"
Unit tests (5)
Unit tests not about one export (8)

Not shown, only used by tests: totalMatchTime, progressPercentage, gameMarkers

PickBan

app/features/tournament-bracket/core/PickBan.ts

No overview comment at the top of the file.

Imports
no other modules
Used by
1 loader1 action6 components4 other

Types

type

Type

no JSDoc:25
export type Type = (typeof types)[number];
interface

PickBanTeam

no JSDoc:27
export interface PickBanTeam {
	id: number;
	seed: number;
}
interface

TurnOfResult

no JSDoc:32
export interface TurnOfResult {
	teamId: number;
	action: ActionType;
	stepCurrent?: number;
	stepTotal?: number;
}
type

MapPoolTeam

:524

A tournament team as far as map pool based pick/ban legality is concerned.

export type MapPoolTeam = {
	id: number;
	mapPool: Array<{ mode: ModeShort; stageId: StageId }> | null;
};
type

CustomFlowValidationError

no JSDoc:779
export type CustomFlowValidationError =
	(typeof CUSTOM_FLOW_VALIDATION_ERRORS)[keyof typeof CUSTOM_FLOW_VALIDATION_ERRORS];

Functions & values

const

types

no JSDoc:19
export const types = [
	"COUNTERPICK",
	"COUNTERPICK_MODE_REPEAT_OK",
	"BAN_2",
	"CUSTOM",
] as const;
function

turnOf

no JSDoc:39
export function turnOf({
	results,
	maps,
	teams,
	mapList,
	pickBanEventCount,
	matchId,
}: {
	results: Array<{ winnerTeamId: number }>;
	maps: TournamentRoundMaps;
	teams: [PickBanTeam, PickBanTeam];
	mapList?: TournamentMapListMap[] | null;
	pickBanEventCount?: number;
	matchId: number;
}): TurnOfResult | null
function

resolveCurrentStep

no JSDoc:234
export function resolveCurrentStep({
	eventCount,
	preSet,
	postGame,
	resultsCount,
}: {
	eventCount: number;
	preSet: CustomPickBanStep[];
	postGame: CustomPickBanStep[];
	resultsCount: number;
}): CustomPickBanStep | null
Unit tests (6)
function

postGameCycleIndex

:274

0-based post-game cycle of an event index (or, for a count of events done, how many cycles have started past the pre-set). Caller must ensure eventIndex >= preSetLength and postGameLength > 0.

export function postGameCycleIndex({
	eventIndex,
	preSetLength,
	postGameLength,
}: {
	eventIndex: number;
	preSetLength: number;
	postGameLength: number;
}): number
function

currentTurnSessionStartedAt

:442

When the currently responsible team's pick/ban session started: on responsibility transitioning to it (consecutive actions by the same team continue it), or at the latest game result as a result always ends the prior session. matchStartedAt when no transitions exist yet, null when there is no current turn or matchStartedAt is unavailable.

export function currentTurnSessionStartedAt({
	currentTurn,
	events,
	results,
	matchStartedAt,
	maps,
	teams,
	matchId,
}: {
	currentTurn: TurnOfResult | null;
	events: Array<{ createdAt: number }>;
	results: Array<{ createdAt: number; winnerTeamId: number }>;
	matchStartedAt: number | null;
	maps: TournamentRoundMaps;
	teams: [PickBanTeam, PickBanTeam];
	matchId: number;
}): number | null
Unit tests (7)
function

isLegal

no JSDoc:488
export function isLegal({
	map,
	...rest
}: MapListWithStatusesArgs & { map: ModeWithStage })
function

isModeLegal

no JSDoc:499
export function isModeLegal({
	mode,
	...rest
}: MapListWithStatusesArgs & { mode: ModeShort })
Unit tests (3)
function

isStageLegal

no JSDoc:508
export function isStageLegal({
	stageId,
	...rest
}: MapListWithStatusesArgs & { stageId: StageId })
function

mapsListWithLegality

no JSDoc:539
export function mapsListWithLegality(args: MapListWithStatusesArgs)
function

validateCustomFlowSection

no JSDoc:787
export function validateCustomFlowSection(
	steps: ValidatableStep[],
	section: "preSet" | "postGame",
): CustomFlowValidationError[]
Unit tests (27)
Unit tests not about one export (33)

Not shown, only used by tests: resolveTeamFromSide, randomWhoTeamIndex, PickBanEvent, CUSTOM_FLOW_VALIDATION_ERRORS

PreparedMaps

app/features/tournament-bracket/core/PreparedMaps.ts

No overview comment at the top of the file.

Imports
ProgressionSkippedRounds
Used by
1 action1 route1 component1 other

Types

type

EliminationBracketType

no JSDoc:58
export type EliminationBracketType =
	| "single_elimination"
	| "double_elimination";

Functions & values

function

resolvePreparedForTheBracket

:16

Prepared maps of the bracket, or of a "sibling" bracket at the same progression depth.

export function resolvePreparedForTheBracket({
	preparedByBracket,
	bracketIdx,
	tournament,
}: {
	preparedByBracket?: (PreparedMaps | null)[];
	bracketIdx: number;
	tournament: Tournament;
})
function

eliminationTeamCountOptions

:99

Elimination team counts that are the "steps" affecting the round count, from currentCount up.

export function eliminationTeamCountOptions({
	type,
	currentCount,
}: {
	type: EliminationBracketType;
	currentCount: number;
})
function

isValidMaxEliminationTeamCount

:115

Whether the count is a known "max" elimination team count.

export function isValidMaxEliminationTeamCount(count: number)
function

eliminationTeamCountPrefill

:129

Prefill for the "expected teams" selection, null when it can't be told yet e.g. registration is still open.

export function eliminationTeamCountPrefill({
	tournament,
	bracketIdx,
}: {
	tournament: Tournament;
	bracketIdx: number;
}): number | null
function

mapListRounds

:168

Rounds that each get a map list. Groups of an elimination bracket share a map list per section and position from its end, so the rounds of the group with the most of them.

export function mapListRounds(data: BracketData)
Unit tests (4)
function

mapListData

:184

{@link mapListRounds} with only their group and matches, the shape a map list is picked against.

export function mapListData(data: BracketData): BracketData
Unit tests (2)
function

trimPreparedEliminationMaps

:206

Trims prepared elimination maps to the actual team count, null if not prepared or prepared for too few.

export function trimPreparedEliminationMaps({
	preparedMaps,
	teamCount,
	...rest
}: TrimPreparedEliminationMapsAgs)
Unit tests not about one export (44)

Progression

app/features/tournament-bracket/core/Progression.ts

No overview comment at the top of the file.

Imports
SkippedRounds
Used by
3 actions3 routes2 repositories8 modules2 components5 other

Types

interface

DBSource

no JSDoc:12
export interface DBSource {
	bracketIdx: number;
	/** E.g. [1, 2] = top 2 teams, [-1] = last placing teams, [] = Swiss early advancers. */
	placements: number[];
	/** Highest value in `placements` means "and every placement after that" ("N+" syntax). Only valid with positive placements. */
	rest?: boolean;
}
interface

EditableSource

no JSDoc:20
export interface EditableSource {
	/** Frontend-only id while editing, replaced by an index once set. See DBSource.bracketIdx. */
	bracketId: string;
	/** User editable, "1-3" and "1,2,3" mean the same. See DBSource.placements for the validated version. */
	placements: string;
}
Only used inside the module
interface

InputBracket

no JSDoc:34
export interface InputBracket extends BracketBase {
	id: string;
	sources?: EditableSource[];
	startTime?: Date;
	/** Already underway */
	disabled?: boolean;
}
type

ValidationError

no JSDoc:47
export type ValidationError =
	// user written placements can not be parsed
	| {
			type: "PLACEMENTS_PARSE_ERROR";
			bracketIdx: number;
	  }
	// from each bracket one placement can lead to only one bracket
	| {
			type: "SAME_PLACEMENT_TO_MULTIPLE_BRACKETS";
			bracketIdxs: number[];
	  }
	// e.g. if 1st and 3rd go somewhere then 2nd must also go somewhere
	| {
			type: "GAP_IN_PLACEMENTS";
			bracketIdxs: number[];
	  }
	// e.g. round robin group size 4 can't have a destination for 5th
	| {
			type: "TOO_MANY_PLACEMENTS";
			bracketIdx: number;
	  }
	// placements above the hard cap are nonsensical and bloat the settings JSON
	| {
			type: "PLACEMENT_TOO_HIGH";
			bracketIdx: number;
	  }
	// two brackets can not have the same name
	| {
			type: "DUPLICATE_BRACKET_NAME";
			bracketIdxs: number[];
	  }
	// bracket name can not be empty
	| {
			type: "NAME_MISSING";
			bracketIdx: number;
	  }
	// negative progression (e.g. losers of first round go somewhere) is only for elimination bracket
	| {
			type: "NEGATIVE_PROGRESSION";
			bracketIdx: number;
	  }
	// a single source can not take both top finishers and eliminated teams
	| {
			type: "MIXED_POSITIVE_NEGATIVE_PLACEMENTS";
			bracketIdx: number;
	  }
	// Swiss bracket with early advance/elimination must have a destination bracket
	| {
			type: "SWISS_EARLY_ADVANCE_NO_DESTINATION";
			bracketIdx: number;
	  }
	// A/B divisions setting is only valid on round robin brackets
	| {
			type: "AB_DIVISIONS_NOT_ROUND_ROBIN";
			bracketIdx: number;
	  }
	// A/B divisions setting is only valid on starting brackets (no sources)
	| {
			type: "AB_DIVISIONS_NOT_STARTING";
			bracketIdx: number;
	  }
	// A/B divisions need an even teamsPerGroup to split each group equally
	| {
			type: "AB_DIVISIONS_ODD_TEAMS_PER_GROUP";
			bracketIdx: number;
	  }
	// empty placements is only valid when sourcing from a Swiss bracket with early advance
	| {
			type: "EMPTY_PLACEMENTS_ON_NON_SWISS";
			bracketIdx: number;
	  }
	// one destination bracket can source each bracket only once
	| {
			type: "DUPLICATE_SOURCE_BRACKET";
			bracketIdx: number;
	  }
	// brackets can not source each other in a loop e.g. A sources B and B sources A
	| {
			type: "CYCLIC_PROGRESSION";
			bracketIdxs: number[];
	  }
	// teams that started in different brackets can never meet
	| {
			type: "MERGED_STARTING_BRACKETS";
			bracketIdx: number;
	  }
	// skipped rounds must belong to the bracket's format and every round depending on a skipped one must be skipped too
	| {
			type: "INVALID_SKIPPED_ROUNDS";
			bracketIdx: number;
	  }
	// elimination brackets can be split into a limited number of groups
	| {
			type: "INVALID_GROUP_COUNT";
			bracketIdx: number;
	  };

Functions & values

function

validatedBracketsToInputFormat

:145

Validated brackets in the format ready for user input.

export function validatedBracketsToInputFormat(
	brackets: ParsedBracket[],
): InputBracket[]
Unit tests (1)
function

placementsToString

:170

[1, 2, 3] -> "1-3", [5, 6] with rest -> "5,6+"

export function placementsToString(placements: number[], rest = false): string
function

validatedBrackets

:210

User-entered bracket progression to validated brackets ready for the database, or errors.

export function validatedBrackets(
	brackets: InputBracket[],
): ParsedBracket[] | ValidationError
function

bracketsToValidationError

:237

Errors in how the progression is laid out.

export function bracketsToValidationError(
	brackets: ParsedBracket[],
): ValidationError | null
function

parsePlacements

:452

"1-3,5+" -> { placements: [1, 2, 3, 5], rest: true }, null if not valid placements syntax. Empty input is the Swiss early advance case.

export function parsePlacements(
	placements: string,
): { placements: number[]; rest: boolean } | null
function

isBrackets

:936

Narrows the return type of Progression.validatedBrackets to a successful validation.

export function isBrackets(
	input: ParsedBracket[] | ValidationError,
): input is ParsedBracket[]
function

isError

:943

Narrows the return type of Progression.validatedBrackets to a failed validation.

export function isError(
	input: ParsedBracket[] | ValidationError,
): input is ValidationError
function

isFinals

:950

Whether the bracket is the final stage that decides the final standings.

export function isFinals(idx: number, brackets: ParsedBracket[])
Unit tests (6)
function

finalsBracketIdx

:957

Index of the last bracket of the main progression starting from startBracketIdx, e.g. a division's top cut.

export function finalsBracketIdx(
	brackets: ParsedBracket[],
	startBracketIdx = 0,
): number
function

hasAbDivisionsFinals

:965

Whether the finals bracket is an A/B divisions round robin.

export function hasAbDivisionsFinals(brackets: ParsedBracket[]): boolean
function

isUnderground

:975

Underground bracket = not part of the main progression, e.g. an optional bracket for early losers.

export function isUnderground(idx: number, brackets: ParsedBracket[])
Unit tests (7)
function

bracketDepth

:1012

Distance from a starting bracket (no sources): starting brackets 0, brackets sourced from them 1, etc.

export function bracketDepth(idx: number, brackets: ParsedBracket[]): number
Unit tests (6)
function

changedBracketProgression

:1074

Indexes of brackets changed in any field.

export function changedBracketProgression(
	oldProgression: ParsedBracket[],
	newProgression: ParsedBracket[],
)
Unit tests (2)
function

changedBracketProgressionFormat

:1093

Whether any field affecting the format changed.

export function changedBracketProgressionFormat(
	oldProgression: ParsedBracket[],
	newProgression: ParsedBracket[],
): boolean
function

changedStartingBrackets

:1116

Returns true if the set of brackets that teams can start in changed

export function changedStartingBrackets(
	oldProgression: ParsedBracket[],
	newProgression: ParsedBracket[],
): boolean
function

bracketIdxsForStandings

:1133

Bracket order for standings: a participant's standing comes from the first bracket in this order they are in. Finals first; a bracket always comes after every bracket it advances teams to, so teams it eliminated end up below those that advanced. Underground brackets are omitted as they only break ties within their source bracket, see tiebrokenByUndergroundBrackets. Considers the brackets reachable from startingBracketIdx, for tournaments with many starting brackets (divisions).

export function bracketIdxsForStandings(
	progression: ParsedBracket[],
	startingBracketIdx = 0,
)
Unit tests (12)
function

bracketsReachableFrom

no JSDoc:1237
export function bracketsReachableFrom(
	bracketIdx: number,
	progression: ParsedBracket[],
	visited: Set<number> = new Set(),
): number[]
function

destinationsFromBracketIdx

no JSDoc:1262
export function destinationsFromBracketIdx(
	sourceBracketIdx: number,
	progression: ParsedBracket[],
): number[]
Unit tests (3)
function

undergroundBracketIdxs

:1282

Underground brackets (taking eliminated teams, negative placements) sourced from the given bracket.

export function undergroundBracketIdxs(
	bracketIdx: number,
	progression: ParsedBracket[],
): number[]
function

destinationByPlacement

no JSDoc:1295
export function destinationByPlacement({
	sourceBracketIdx,
	placement,
	progression,
}: {
	sourceBracketIdx: number;
	placement: number;
	progression: ParsedBracket[];
}): number | null
Unit tests (4)
function

sortedSourcesForSeeding

:1340

Orders sources for seeding: a better placement in a shared ancestor bracket seeds above a longer route there, e.g. the top 2 of pools directly over the winners of a redemption bracket sourcing pools placements 3-4. Sources sharing no ancestor keep their relative order.

export function sortedSourcesForSeeding(
	sources: DBSource[],
	progression: ParsedBracket[],
): DBSource[]
Unit tests (4)
Unit tests not about one export (56)

Seeding

app/features/tournament-bracket/core/Seeding.ts

No overview comment at the top of the file.

Imports
no other modules
Used by
1 module

Types

interface

FollowUpBracketSource

no JSDoc:4
export interface FollowUpBracketSource {
	/** Best placements first. Tied placements (e.g. the winner of each group) form the tiers seeding keeps intact. */
	standings: Array<{
		tournamentTeamId: number;
		placement: number;
	}>;
	/** Pairs of teams that already faced each other in the source bracket */
	encounters: Array<[number, number]>;
}

Functions & values

function

forFollowUpBracket

:42

Reorders teams advancing into an elimination bracket so that:

- teams sharing a group spread evenly and can rematch only as late as the bracket allows (e.g. 4 groups of 4 into a 16 bracket: one team per group in every quarter, rematch in the semis at earliest) - teams that already faced each other do not rematch in round 1 (e.g. a single Swiss group feeding a top cut) - placement tiers stay intact: a team never takes a seed reserved for a better placement, except in the bottom half of a bracket sourced from a single group, where placements may swap to avoid rematches

The incoming order changes as little as possible. Unsatisfiable constraints are relaxed step by step, as the last resort the incoming order is returned.

An elimination bracket split into groups is treated as its groups' brackets side by side.

export function forFollowUpBracket({
	teams,
	sources,
	groupKeyByTeamId,
	groupCount = 1,
}: {
	/** tournament team ids in their incoming seed order (best placements first) */
	teams: number[];
	sources: FollowUpBracketSource[];
	/** The group each team played in, teams sharing a key get spread. Teams without one are not spread. */
	groupKeyByTeamId: Map<number, string>;
	/** Groups the elimination bracket is split into */
	groupCount?: number;
}): number[]
Unit tests (19)

SkippedRounds

app/features/tournament-bracket/core/SkippedRounds.ts

No overview comment at the top of the file.

Imports
no other modules
Used by
3 modules1 component4 other

Functions & values

const

SKIPPABLE_ROUNDS

:7

Named rounds that can be left unplayed, in the order they are played.

export const SKIPPABLE_ROUNDS = {
	single_elimination: ["SEMIS", "FINALS", "THIRD_PLACE_MATCH"],
	double_elimination: [
		"WB_FINALS",
		"LB_SEMIS",
		"LB_FINALS",
		"GRAND_FINALS",
		"BRACKET_RESET",
	],
} as const satisfies Record<EliminationType, SkippableRound[]>;
const

ALL_SKIPPABLE_ROUNDS

:19

Every round that can be skipped in some bracket type.

export const ALL_SKIPPABLE_ROUNDS = [
	...SKIPPABLE_ROUNDS.single_elimination,
	...SKIPPABLE_ROUNDS.double_elimination,
] as const;
function

prerequisitesOf

:37

Rounds that must be played for the given round to be played.

export function prerequisitesOf(round: SkippableRound): SkippableRound[]
function

withSkipped

:44

Skipped rounds after skipping round, which also skips every round depending on it.

export function withSkipped(
	type: EliminationType,
	skipped: SkippableRound[],
	round: SkippableRound,
): SkippableRound[]
function

withPlayed

:53

Skipped rounds after playing round, which also plays every round it depends on.

export function withPlayed(
	type: EliminationType,
	skipped: SkippableRound[],
	round: SkippableRound,
): SkippableRound[]
function

isValid

:67

Whether every round of the bracket type can be skipped and every round depending on a skipped one is skipped too.

export function isValid(
	type: EliminationType,
	skipped: SkippableRound[],
): boolean
function

normalized

:80

Skipped rounds of the bracket type with their dependents included, in play order. Rounds of another bracket type are left out.

export function normalized(
	type: EliminationType,
	skipped: SkippableRound[] | undefined,
): SkippableRound[]
function

skippedRoundNumbers

:98

Round numbers of a section of one elimination group that are skipped. Rounds are identified from the end of the section, so e.g. "FINALS" is always the last round of the bracket, whatever its size.

  • @param

    roundCount Rounds the section has when nothing is skipped.

export function skippedRoundNumbers({
	type,
	section,
	roundCount,
	skipped,
}: {
	type: EliminationType;
	section: RoundSection;
	roundCount: number;
	skipped: SkippableRound[];
}): Set<number>

Tournament

serverapp/features/tournament-bracket/core/Tournament.server.ts

No overview comment at the top of the file.

Imports
RunningTournaments.serverTournament
Used by
19 loaders17 actions14 routes2 modules4 components2 routines2 scripts8 other

Types

type

TournamentData

no JSDoc:119
export type TournamentData = NonNullable<Unwrapped<typeof tournamentData>>;
type

TournamentLayoutData

:122

What the layout ships: everything every view needs. Match data is loaded per bracket by the bracket views.

export type TournamentLayoutData = {
	ctx: TournamentData["ctx"];
	bracketsMeta: BracketDerivedMeta[];
};
Loaders/to/:id
type

TournamentDataTeam

:128

No per member profile data, map pool or invite code, see {@link tournamentTeamsFullCached} for those.

export type TournamentDataTeam = Omit<
	TournamentRepository.FindById["teams"][number],
	"teamLogoUrl" | "pickupAvatarUrl" | "inviteCode"
> & {
	/**
	 * Linked team logo, falling back to the pickup avatar once started. Views showing pickup avatars
	 * before that (own team, organizer) read them off {@link tournamentTeamsFullCached}, censored per viewer.
	 */
	logoUrl: string | null;
};
type

TournamentTeamFull

:413

Team with its full roster.

export type TournamentTeamFull = Unwrapped<typeof tournamentTeamsFullCached>;

Functions & values

function

tournamentData

:42

Everything a tournament is made of including brackets and streams.

export async function tournamentData(tournamentId: number)
function

fetchTournamentStreams

:81

Read fresh, bypassing the cache whose copy (read once per fill) only serves server-side consumers.

export async function fetchTournamentStreams(
	tournamentId: number,
): Promise<TournamentStream[]>
function

requireTournamentVisible

:151

Every loader under the tournament layout must run this (normally via {@link tournamentFromParams}): single fetch reaches each without the layout loader's check.

  • @throws

    {Response} 404 if the tournament is a draft the user is not an organizer of

export function requireTournamentVisible({
	ctx,
	user,
}: {
	ctx: TournamentVisibilityCtx;
	user: OptionalIdObject;
})
function

canSeeTournamentFriendCodes

:170

Organizers see the participants' friend codes only for a while after the start. Leagues run for many weeks, so theirs stay visible for longer.

export function canSeeTournamentFriendCodes({
	ctx,
	user,
}: {
	ctx: TournamentFriendCodeCtx;
	user: OptionalIdObject;
})
function

isTournamentTeamInfoRevealed

:187

Pickup avatars and map pools of teams are only revealed to organizers (and the team itself) before the start.

export function isTournamentTeamInfoRevealed({
	tournament,
	user,
}: {
	tournament: Pick<TournamentData, "ctx" | "data">;
	user: OptionalIdObject;
})
function

requireTournamentOrganizer

:201

Guards a single _action branch; whole-route guards use {@link tournamentFromParams} with for: "organizer".

export function requireTournamentOrganizer(
	tournament: Tournament,
	user: AuthenticatedUser,
	message = "Not an organizer",
)
function

requireTournamentAdmin

:210

Throws an error toast unless the user is an admin.

export function requireTournamentAdmin(
	tournament: Tournament,
	user: AuthenticatedUser,
)
function

tournamentFromParams

:228

Preamble of to.$id.* loaders and actions: parses the id (404 on invalid), loads and guards.

- view: anyone the tournament is visible to; shared cached instance, amortizing bracket building. - action: any logged-in user; fresh read for actions doing their own per _action authorization. - organizer / admin: like action but others are redirected to the tournament front page.

export async function tournamentFromParams(
	params: Params<string>,
	opts: { for: "view" },
): Promise<{
	tournament: Tournament;
	tournamentId: number;
	user: AuthenticatedUser | undefined;
}>;
function

notifyTournamentStatusChanged

:288

Prompts the users' clients to refetch their header status after a change to the tournament. Fills the (just cleared) cache and syncs the registry from that one rebuild so the refetch reads post-change state and the revalidation that follows the action finds a warm cache.

export async function notifyTournamentStatusChanged(
	tournamentId: number,
	userIds: number[],
)
function

tournamentDataCached

no JSDoc:313
export async function tournamentDataCached(tournamentId: number)
function

tournamentSharedCached

:322

Shared by every request for the lifetime of the cache entry, so brackets are built once per fill.

export async function tournamentSharedCached(tournamentId: number)
function

bracketsMetaCached

:346

State of every bracket without the match data it derives from.

export async function bracketsMetaCached(
	tournamentId: number,
): Promise<BracketDerivedMeta[]>
Loaders/to/:id
function

serializeBracket

:356

In the shape {@link Tournament.withBrackets} revives. With a groupId only that group's rounds and matches are included, every group still listed so the view can offer switching.

export function serializeBracket(
	bracket: Bracket,
	args?: { groupId: number | null },
): SerializedBracket
Unit tests (3)
function

tournamentTeamsFullCached

:426

Full rosters censored for the viewer. Own cache slice so the much smaller layout data needn't carry them.

export async function tournamentTeamsFullCached({
	user,
	tournamentId,
}: {
	user?: { id: number };
	tournamentId: number;
})
function

tournamentTeamsFullInSeedOrder

:459

{@link tournamentTeamsFullCached} in seed order, which is not the order the rows come back in.

export async function tournamentTeamsFullInSeedOrder({
	tournament,
	user,
}: {
	tournament: Tournament;
	user?: { id: number };
})
function

clearAllTournamentDataCache

no JSDoc:528
export function clearAllTournamentDataCache()
function

refreshRunningTournaments

:606

E2E workers call this (via /refresh-caches) after writing tournaments straight into the database file.

export async function refreshRunningTournaments()

Tournament

app/features/tournament-bracket/core/Tournament.ts

No overview comment at the top of the file.

Imports
TeamPickProgressionCheckInSeeding
Used by
8 loaders7 actions6 routes6 modules8 components14 other

Types

type

OptionalIdObject

no JSDoc:39
export type OptionalIdObject = { id: number } | undefined;
type

BracketDerivedMeta

:42

Bracket state derivable only from its match data, shipped to views that render without it.

export type BracketDerivedMeta = {
	/** Stage id of a started bracket, placeholder id of a bracket that has not been started. */
	id: number;
	createdAt: number | null;
	preview: boolean;
	everyMatchOver: boolean;
	participantTournamentTeamIds: number[];
	teamsPendingCheckIn: number[] | null;
	previewSeeding: number[] | null;
};
type

BracketMeta

:54

A bracket's identity and state without its match data. See {@link Tournament.bracketsMeta}.

export type BracketMeta = BracketDerivedMeta & {
	idx: number;
	name: string;
	type: Tables["TournamentStage"]["type"];
	sources: Progression.ParsedBracket["sources"];
	settings: TournamentStageSettings | null;
	requiresCheckIn: boolean;
	startTime: Date | null;
	isUnderground: boolean;
	isFinals: boolean;
	isStartingBracket: boolean;
	enoughTeams: boolean;
};
type

SerializedBracket

:69

One bracket as a route loader ships it, ready to be revived by {@link Tournament.withBrackets}.

export type SerializedBracket = {
	id: number;
	idx: number;
	preview: boolean;
	data: TournamentData["data"];
	type: Tables["TournamentStage"]["type"];
	participantsReady?: boolean;
	name: string;
	teamsPendingCheckIn?: number[];
	createdAt: number | null;
	sources?: { bracketIdx: number; placements: number[] }[];
	previewSeeding?: number[];
	settings: TournamentStageSettings | null;
	requiresCheckIn: boolean;
	startTime: number | null;
};
type

TournamentStream

:87

One live stream of the tournament: a participant's stream or an official cast stream.

export type TournamentStream = StreamAccount & {
	videoId: string | null;
	thumbnailUrl: string;
	viewerCount: number;
	userId: number | null;
	teamId: number | null;
	user: {
		id: number;
		username: string;
		customUrl: string | null;
		avatar: string | null;
	} | null;
};
type

TournamentTeamMemberProgressStatus

:118

The progress status of a team member in a running tournament, as resolved by {@link Tournament.teamMemberOfProgressStatus}.

export type TournamentTeamMemberProgressStatus = NonNullable<
	ReturnType<Tournament["teamMemberOfProgressStatus"]>
>;

Functions & values

class

Tournament

:123

Utilities on top of the tournament's data. Updating started bracket data is the engine's job (core/engine).

export class Tournament
Unit tests not about one export (48)