Skip to main content

InstantDB Schema

This is the full InstantDB schema for Sleet.gg.

Schema Definition

// sleet.gg

import { i } from "@instantdb/core";

const graph = i.schema({
entities: {
tournaments: i.entity({
name: i.string(),
visibility: i.string(),
slug: i.string().unique(),
description: i.string(),
icon: i.string().optional(),
banner: i.string().optional(),
location: i
.json<{
lat: number;
lon: number;
}>()
.optional(),
}),
tournamentEntrants: i.entity({
joinedAt: i.date(),
}),
// EVENT entrants
entrants: i.entity({
role: i.string(),
teamRole: i.string<"captain" | "member">().optional(),
}),
events: i.entity({
name: i.string(),
description: i.string(),
status: i.string<"pending" | "started" | "completed">(),
slug: i.string(),
minTeamSize: i.number(),
maxTeamSize: i.number(),
customGame: i.string().optional(),
bracketSetup:
i.json<
{
stage: number;
gameCount: number;
bracketType:
| "single-elimination"
| "double-elimination"
| "round-robin";
}[]
>(),
numberOfPools: i.number(),
startsAt: i.string(),
endedAt: i.string().optional(),
checkInMinutes: i.number().optional(),
advancedSettings: i
.json<{
requiredTwitchSub?: {
id: string;
slug: string;
name: string;
image: string;
};
requiredDiscordServer?: {
id: string;
name: string;
image?: string;
};
requireSteamConnection?: boolean;
amateurBracket?: {
enabled: boolean;
criteria: "wins" | "placement" | "pools";
winThreshold: number;
placementThreshold: number;
bracketSetup: {
stage: number;
gameCount: number;
bracketType:
| "single-elimination"
| "double-elimination"
| "round-robin";
}[];
numberOfPools: number;
checkInMinutes: number;
};
}>()
.clientRequired(),
}),
games: i.entity({
gameId: i.number().unique(),
name: i.string(),
coverId: i.string(),
}),
matches: i.entity({
round: i.number(),
matchNumber: i.number(),
pool: i.number(),
stage: i.number(),
status: i.string().clientRequired(),
isGrands: i.boolean().optional(),
}),
chatData: i.entity({
coinFlip: i
.json<{
type: "request" | "complete";
requesterId: string;
requesterName: string;
}>()
.optional(),
}),
standings: i.entity({
place: i.number(),
}),
messages: i.entity({
body: i.string(),
createdAt: i.date().clientRequired(),
}),
scores: i.entity({
score: i.number(),
}),
teams: i.entity({
name: i.string().optional(),
seed: i.number(),
checkedIn: i.boolean().optional(),
}),
scoreReports: i.entity({
score: i.number(),
reportedAt: i.string(),
}),
profiles: i.entity({
name: i.string(),
image: i.string().optional(),
createdAt: i.date(),
extraData: i.json<{
earlyAdopter?: boolean;
twitchName?: string;
steam?: {
id: string;
name: string;
avatar: string;
url: string;
};
}>(),
}),
teamInvites: i.entity({
status: i.string(), // "pending" | "accepted"
}),
},
links: {
tournamentsHaveManyEntrants: {
forward: {
on: "tournaments",
has: "many",
label: "entrants",
},
reverse: {
on: "tournamentEntrants",
has: "one",
label: "tournament",
onDelete: "cascade",
},
},
tournamentsHaveManyAdmins: {
forward: {
on: "tournaments",
has: "many",
label: "admins",
},
reverse: {
on: "profiles",
has: "many",
label: "adminTournaments",
},
},
eventsHaveManyEntrants: {
forward: {
on: "events",
has: "many",
label: "entrants",
},
reverse: {
on: "entrants",
has: "one",
label: "event",
},
},
tournamentsHaveManyEvents: {
forward: {
on: "tournaments",
has: "many",
label: "events",
},
reverse: {
on: "events",
has: "one",
label: "tournament",
onDelete: "cascade",
},
},
eventsHaveManyMatches: {
forward: {
on: "events",
has: "many",
label: "matches",
},
reverse: {
on: "matches",
has: "one",
label: "event",
onDelete: "cascade",
},
},
eventsHaveManyGames: {
forward: {
on: "events",
has: "one",
label: "game",
},
reverse: {
on: "games",
has: "many",
label: "events",
},
},
matchesHaveManyTeams: {
forward: {
on: "matches",
has: "many",
label: "teams",
},
reverse: {
on: "teams",
has: "many",
label: "matches",
},
},
teamsHaveAnEvent: {
forward: {
on: "teams",
has: "one",
label: "event",
onDelete: "cascade",
},
reverse: {
on: "events",
has: "many",
label: "teams",
},
},
matchesHaveAWin: {
forward: {
on: "matches",
has: "one",
label: "win",
},
reverse: {
on: "matches",
has: "many",
label: "prevWins",
},
},
matchesHaveALoss: {
forward: {
on: "matches",
has: "one",
label: "loss",
},
reverse: {
on: "matches",
has: "many",
label: "prevLosses",
},
},
matchesHaveAWinner: {
forward: {
on: "matches",
has: "one",
label: "winner",
},
reverse: {
on: "teams",
has: "many",
label: "wins",
},
},
matchesHaveALoser: {
forward: {
on: "matches",
has: "one",
label: "loser",
},
reverse: {
on: "teams",
has: "many",
label: "losses",
},
},
matchesHaveScores: {
forward: {
on: "matches",
has: "many",
label: "scores",
},
reverse: {
on: "scores",
has: "one",
label: "match",
onDelete: "cascade",
},
},
matchesHaveScoreReports: {
forward: {
on: "matches",
has: "many",
label: "scoreReports",
},
reverse: {
on: "scoreReports",
has: "one",
label: "match",
onDelete: "cascade",
},
},
scoreReportsHaveAReporter: {
forward: {
on: "scoreReports",
has: "one",
label: "reporter",
},
reverse: {
on: "profiles",
has: "many",
label: "scoreReports",
},
},
eventsHaveStandings: {
forward: {
on: "events",
has: "many",
label: "standings",
},
reverse: {
on: "standings",
has: "one",
label: "event",
onDelete: "cascade",
},
},
standingsHaveATeam: {
forward: {
on: "teams",
has: "many",
label: "standings",
},
reverse: {
on: "standings",
has: "one",
label: "team",
},
},
messagesHaveASender: {
forward: {
on: "messages",
has: "one",
label: "sender",
},
reverse: {
on: "profiles",
has: "many",
label: "sentMessages",
},
},
messagesHaveAMatch: {
forward: {
on: "messages",
has: "one",
label: "match",
onDelete: "cascade",
},
reverse: {
on: "matches",
has: "many",
label: "messages",
},
},
teamsHaveManyEntrants: {
forward: {
on: "teams",
has: "many",
label: "entrants",
},
reverse: {
on: "entrants",
has: "one",
label: "team",
},
},
teamsHaveScores: {
forward: {
on: "teams",
has: "many",
label: "scores",
},
reverse: {
on: "scores",
has: "one",
label: "team",
},
},
teamsHaveScoreReports: {
forward: {
on: "teams",
has: "many",
label: "scoreReports",
},
reverse: {
on: "scoreReports",
has: "one",
label: "team",
},
},
entrantsAreAProfile: {
forward: {
on: "entrants",
has: "one",
label: "profile",
},
reverse: {
on: "profiles",
has: "many",
label: "entrants",
},
},
tournamentEntrantsAreAProfile: {
forward: {
on: "tournamentEntrants",
has: "one",
label: "profile",
},
reverse: {
on: "profiles",
has: "many",
label: "tournamentEntrants",
},
},
teamInvitesHaveATeam: {
forward: {
on: "teamInvites",
has: "one",
label: "team",
onDelete: "cascade",
},
reverse: {
on: "teams",
has: "many",
label: "invites",
},
},
teamInvitesHaveAProfile: {
forward: {
on: "teamInvites",
has: "one",
label: "profile",
},
reverse: {
on: "profiles",
has: "many",
label: "teamInvites",
},
},
matchesHaveChatData: {
forward: {
on: "matches",
has: "one",
label: "chatData",
},
reverse: {
on: "chatData",
has: "one",
label: "match",
onDelete: "cascade",
},
},
eventsHaveAmateurBracket: {
forward: {
on: "events",
has: "many",
label: "amateurBrackets",
},
reverse: {
on: "events",
has: "one",
label: "mainBracket",
},
},
},
});

export default graph;

Key Entities

Tournaments

Top-level containers for events. Profiles join tournaments via tournamentEntrants.

Events

Specific competitions within tournaments. Profiles join events via entrants.

Teams & Entrants

Teams are the primary entity in representing a player in a brackets. Teams can have any number of profiles linked.

Matches

Matches have an event and a team. Recursively linked via win and loss.

Profiles

Holds most user data.

This schema powers all the data available through the API query endpoint.