fix: update match configuration to bestOf 5 and scoringType to score_v2

This commit is contained in:
2026-02-28 06:38:05 +01:00
parent e99444b157
commit fb9140a03f
555 changed files with 124715 additions and 1 deletions

View File

@@ -0,0 +1,69 @@
import { b as private_env } from "./shared-server.js";
import "./index2.js";
const CACHE_TTL = 10 * 60 * 1e3;
const MAX_ENTRIES = 500;
const cache = /* @__PURE__ */ new Map();
function getCached(key) {
const entry = cache.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
cache.delete(key);
return null;
}
return entry.data;
}
function setCache(key, data, ttl = CACHE_TTL) {
if (cache.size >= MAX_ENTRIES) {
const firstKey = cache.keys().next().value;
if (firstKey) cache.delete(firstKey);
}
cache.set(key, { data, expiresAt: Date.now() + ttl });
}
let clientToken = null;
let clientTokenExpiresAt = 0;
async function getClientToken() {
if (clientToken && Date.now() < clientTokenExpiresAt - 6e4) {
return clientToken;
}
const res = await fetch("https://osu.ppy.sh/oauth/token", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
client_id: private_env.OSU_CLIENT_ID,
client_secret: private_env.OSU_CLIENT_SECRET,
grant_type: "client_credentials",
scope: "identify public"
})
});
if (!res.ok) {
throw new Error(`Failed to get osu! client token: ${res.status} ${res.statusText}`);
}
const data = await res.json();
clientToken = data.access_token;
clientTokenExpiresAt = Date.now() + data.expires_in * 1e3;
return clientToken;
}
async function osuFetch(path, token) {
const t = await getClientToken();
const res = await fetch(`https://osu.ppy.sh/api/v2${path}`, {
headers: { Authorization: `Bearer ${t}` }
});
if (!res.ok) {
throw new Error(`osu! API ${path}: ${res.status} ${res.statusText}`);
}
return res.json();
}
async function getBeatmap(beatmapId) {
const cacheKey = `beatmap:${beatmapId}`;
const cached = getCached(cacheKey);
if (cached) return cached;
const data = await osuFetch(`/beatmaps/${beatmapId}`);
setCache(cacheKey, data);
return data;
}
export {
getBeatmap as g
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
import { CompiledQuery, DefaultQueryCompiler, DEFAULT_MIGRATION_TABLE, DEFAULT_MIGRATION_LOCK_TABLE, sql } from "kysely";
var BunSqliteAdapter = class {
get supportsCreateIfNotExists() {
return true;
}
get supportsTransactionalDdl() {
return false;
}
get supportsReturning() {
return true;
}
async acquireMigrationLock() {
}
async releaseMigrationLock() {
}
get supportsOutput() {
return true;
}
};
var BunSqliteDriver = class {
#config;
#connectionMutex = new ConnectionMutex();
#db;
#connection;
constructor(config) {
this.#config = { ...config };
}
async init() {
this.#db = this.#config.database;
this.#connection = new BunSqliteConnection(this.#db);
if (this.#config.onCreateConnection) await this.#config.onCreateConnection(this.#connection);
}
async acquireConnection() {
await this.#connectionMutex.lock();
return this.#connection;
}
async beginTransaction(connection) {
await connection.executeQuery(CompiledQuery.raw("begin"));
}
async commitTransaction(connection) {
await connection.executeQuery(CompiledQuery.raw("commit"));
}
async rollbackTransaction(connection) {
await connection.executeQuery(CompiledQuery.raw("rollback"));
}
async releaseConnection() {
this.#connectionMutex.unlock();
}
async destroy() {
this.#db?.close();
}
};
var BunSqliteConnection = class {
#db;
constructor(db) {
this.#db = db;
}
executeQuery(compiledQuery) {
const { sql: sql$1, parameters } = compiledQuery;
const stmt = this.#db.prepare(sql$1);
return Promise.resolve({ rows: stmt.all(parameters) });
}
async *streamQuery() {
throw new Error("Streaming query is not supported by SQLite driver.");
}
};
var ConnectionMutex = class {
#promise;
#resolve;
async lock() {
while (await this.#promise) await this.#promise;
this.#promise = new Promise((resolve) => {
this.#resolve = resolve;
});
}
unlock() {
const resolve = this.#resolve;
this.#promise = void 0;
this.#resolve = void 0;
resolve?.();
}
};
var BunSqliteIntrospector = class {
#db;
constructor(db) {
this.#db = db;
}
async getSchemas() {
return [];
}
async getTables(options = { withInternalKyselyTables: false }) {
let query = this.#db.selectFrom("sqlite_schema").where("type", "=", "table").where("name", "not like", "sqlite_%").select("name").$castTo();
if (!options.withInternalKyselyTables) query = query.where("name", "!=", DEFAULT_MIGRATION_TABLE).where("name", "!=", DEFAULT_MIGRATION_LOCK_TABLE);
const tables = await query.execute();
return Promise.all(tables.map(({ name }) => this.#getTableMetadata(name)));
}
async getMetadata(options) {
return { tables: await this.getTables(options) };
}
async #getTableMetadata(table) {
const db = this.#db;
const autoIncrementCol = (await db.selectFrom("sqlite_master").where("name", "=", table).select("sql").$castTo().execute())[0]?.sql?.split(/[\(\),]/)?.find((it) => it.toLowerCase().includes("autoincrement"))?.split(/\s+/)?.[0]?.replace(/["`]/g, "");
return {
name: table,
columns: (await db.selectFrom(sql`pragma_table_info(${table})`.as("table_info")).select([
"name",
"type",
"notnull",
"dflt_value"
]).execute()).map((col) => ({
name: col.name,
dataType: col.type,
isNullable: !col.notnull,
isAutoIncrementing: col.name === autoIncrementCol,
hasDefaultValue: col.dflt_value != null
})),
isView: true
};
}
};
var BunSqliteQueryCompiler = class extends DefaultQueryCompiler {
getCurrentParameterPlaceholder() {
return "?";
}
getLeftIdentifierWrapper() {
return '"';
}
getRightIdentifierWrapper() {
return '"';
}
getAutoIncrement() {
return "autoincrement";
}
};
var BunSqliteDialect = class {
#config;
constructor(config) {
this.#config = { ...config };
}
createDriver() {
return new BunSqliteDriver(this.#config);
}
createQueryCompiler() {
return new BunSqliteQueryCompiler();
}
createAdapter() {
return new BunSqliteAdapter();
}
createIntrospector(db) {
return new BunSqliteIntrospector(db);
}
};
export {
BunSqliteDialect
};

View File

@@ -0,0 +1,350 @@
import { d as db, b as match, e as matchParticipant, t as teamMember, f as matchParticipantPlayer, g as matchGame, h as matchGameScore, i as matchQueue, p as playerRating } from "./index2.js";
import { eq, sql, asc } from "drizzle-orm";
const MATCH_STATES = {
CREATED: "CREATED",
LOBBY: "LOBBY",
ROLLING: "ROLLING",
PICKING: "PICKING",
PLAYING: "PLAYING",
FINISHED: "FINISHED",
CANCELLED: "CANCELLED"
};
const GAME_STATES = {
PLAYING: "PLAYING",
FINISHED: "FINISHED"
};
async function joinQueue(userId, teamId) {
let rating = await db.query.playerRating.findFirst({
where: eq(playerRating.userId, userId)
});
if (!rating) {
[rating] = await db.insert(playerRating).values({ userId, elo: 1e3 }).returning();
}
const existing = await db.query.matchQueue.findFirst({
where: eq(matchQueue.userId, userId)
});
if (existing) throw new Error("Already in queue");
await db.insert(matchQueue).values({
userId,
teamId,
elo: rating.elo
});
return tryMatchFromQueue();
}
async function leaveQueue(userId) {
await db.delete(matchQueue).where(eq(matchQueue.userId, userId));
}
async function getQueueStatus(userId) {
const entry = await db.query.matchQueue.findFirst({
where: eq(matchQueue.userId, userId)
});
const count = await db.select({ count: sql`count(*)` }).from(matchQueue);
return {
inQueue: !!entry,
queueSize: Number(count[0].count),
joinedAt: entry?.joinedAt ?? null
};
}
async function tryMatchFromQueue() {
const queue = await db.query.matchQueue.findMany({
orderBy: asc(matchQueue.elo)
});
if (queue.length < 2) return null;
let bestPair = null;
let smallestGap = Infinity;
for (let i = 0; i < queue.length - 1; i++) {
const gap = Math.abs(queue[i].elo - queue[i + 1].elo);
if (gap < smallestGap) {
smallestGap = gap;
bestPair = [queue[i], queue[i + 1]];
}
}
if (!bestPair) return null;
const avgElo = (bestPair[0].elo + bestPair[1].elo) / 2;
const selectedPool = await selectMappoolForRating(avgElo);
if (!selectedPool) throw new Error("No mappools available");
await db.delete(matchQueue).where(eq(matchQueue.userId, bestPair[0].userId));
await db.delete(matchQueue).where(eq(matchQueue.userId, bestPair[1].userId));
const created = await createMatch({
name: "Ranked Match",
config: { bestOf: 7, teamSize: 1, scoringType: "score" },
mappoolId: selectedPool.id,
teams: [bestPair[0].teamId, bestPair[1].teamId],
createdBy: "system"
});
return created;
}
async function selectMappoolForRating(avgElo) {
const pools = await db.query.mappool.findMany({
with: { slots: true }
});
if (pools.length === 0) return null;
const targetStars = 3 + (avgElo - 800) / 400 * 4;
const clampedTarget = Math.max(2, Math.min(8, targetStars));
let bestPool = pools[0];
let bestDiff = Infinity;
for (const pool of pools) {
if (pool.slots.length === 0) continue;
const avgStars = pool.slots.reduce((sum, s) => sum + (s.starRating ?? 0), 0) / pool.slots.length;
const diff = Math.abs(avgStars - clampedTarget);
if (diff < bestDiff) {
bestDiff = diff;
bestPool = pool;
}
}
return bestPool;
}
async function createMatch(opts) {
const { name, config, mappoolId, teams, createdBy } = opts;
const [created] = await db.insert(match).values({
name,
config,
mappoolId,
createdBy,
state: MATCH_STATES.CREATED
}).returning();
for (let i = 0; i < teams.length; i++) {
const [participant] = await db.insert(matchParticipant).values({
matchId: created.id,
teamId: teams[i],
slot: i + 1
}).returning();
const members = await db.query.teamMember.findMany({
where: eq(teamMember.teamId, teams[i])
});
for (const member of members) {
await db.insert(matchParticipantPlayer).values({
participantId: participant.id,
userId: member.userId
});
}
}
return getMatchFull(created.id);
}
async function moveToLobby(matchId, osuLobbyId) {
const m = await getMatchOrThrow(matchId);
assertState(m.state, MATCH_STATES.CREATED);
await db.update(match).set({
state: MATCH_STATES.LOBBY,
osuLobbyId: osuLobbyId ?? null,
startedAt: /* @__PURE__ */ new Date()
}).where(eq(match.id, matchId));
return getMatchFull(matchId);
}
async function moveToRolling(matchId) {
const m = await getMatchOrThrow(matchId);
assertState(m.state, MATCH_STATES.LOBBY);
await db.update(match).set({ state: MATCH_STATES.ROLLING }).where(eq(match.id, matchId));
return getMatchFull(matchId);
}
async function submitRoll(matchId, participantId, value) {
const m = await getMatchOrThrow(matchId);
assertState(m.state, MATCH_STATES.ROLLING);
await db.update(matchParticipant).set({ rollValue: value }).where(eq(matchParticipant.id, participantId));
const participants = await db.query.matchParticipant.findMany({
where: eq(matchParticipant.matchId, matchId)
});
const allRolled = participants.every(
(p) => p.id === participantId ? true : p.rollValue !== null
);
if (allRolled) {
const sorted = [...participants].map((p) => ({
...p,
rollValue: p.id === participantId ? value : p.rollValue
})).sort((a, b) => b.rollValue - a.rollValue);
for (let i = 0; i < sorted.length; i++) {
await db.update(matchParticipant).set({ pickOrder: i + 1 }).where(eq(matchParticipant.id, sorted[i].id));
}
await db.update(match).set({ state: MATCH_STATES.PICKING }).where(eq(match.id, matchId));
}
return getMatchFull(matchId);
}
async function pickMap(matchId, participantId, mappoolSlotId) {
const m = await getMatchOrThrow(matchId);
assertState(m.state, MATCH_STATES.PICKING);
const participants = await db.query.matchParticipant.findMany({
where: eq(matchParticipant.matchId, matchId)
});
const totalGames = await db.query.matchGame.findMany({
where: eq(matchGame.matchId, matchId)
});
const expectedPicker = getExpectedPicker(participants, totalGames.length);
if (expectedPicker.id !== participantId) {
throw new Error("Not your turn to pick");
}
const alreadyPlayed = totalGames.some((g) => g.mappoolSlotId === mappoolSlotId);
if (alreadyPlayed) {
throw new Error("This map has already been played");
}
const [game] = await db.insert(matchGame).values({
matchId,
gameNumber: totalGames.length + 1,
mappoolSlotId,
pickedByParticipantId: participantId,
state: GAME_STATES.PLAYING,
startedAt: /* @__PURE__ */ new Date()
}).returning();
await db.update(match).set({ state: MATCH_STATES.PLAYING }).where(eq(match.id, matchId));
return game;
}
async function submitGameScores(matchGameId, scores) {
const game = await db.query.matchGame.findFirst({
where: eq(matchGame.id, matchGameId)
});
if (!game) throw new Error("Game not found");
if (game.state !== GAME_STATES.PLAYING) throw new Error(`Game is ${game.state}, not PLAYING`);
for (const s of scores) {
await db.insert(matchGameScore).values({
matchGameId,
playerId: s.playerId,
score: s.score,
accuracy: s.accuracy ?? 0,
maxCombo: s.maxCombo ?? 0,
count300: s.count300 ?? 0,
count100: s.count100 ?? 0,
count50: s.count50 ?? 0,
countMiss: s.countMiss ?? 0,
mods: s.mods ?? [],
passed: s.passed ?? true
});
}
const allScores = await db.query.matchGameScore.findMany({
where: eq(matchGameScore.matchGameId, matchGameId),
with: { player: true }
});
const participantScores = /* @__PURE__ */ new Map();
for (const s of allScores) {
const pid = s.player.participantId;
participantScores.set(pid, (participantScores.get(pid) ?? 0) + s.score);
}
let winnerId = null;
let highScore = -1;
for (const [pid, total] of participantScores) {
if (total > highScore) {
highScore = total;
winnerId = pid;
}
}
await db.update(matchGame).set({
state: GAME_STATES.FINISHED,
winnerParticipantId: winnerId,
finishedAt: /* @__PURE__ */ new Date()
}).where(eq(matchGame.id, matchGameId));
if (winnerId) {
const participant = await db.query.matchParticipant.findFirst({
where: eq(matchParticipant.id, winnerId)
});
if (participant) {
await db.update(matchParticipant).set({ score: participant.score + 1 }).where(eq(matchParticipant.id, winnerId));
}
}
const m = await getMatchOrThrow(game.matchId);
const config = m.config;
const winsNeeded = Math.ceil(config.bestOf / 2);
const participants = await db.query.matchParticipant.findMany({
where: eq(matchParticipant.matchId, game.matchId)
});
const matchWinner = participants.find((p) => p.score >= winsNeeded);
if (matchWinner) {
await db.update(match).set({
state: MATCH_STATES.FINISHED,
winnerId: matchWinner.teamId,
finishedAt: /* @__PURE__ */ new Date()
}).where(eq(match.id, game.matchId));
await updateElo(participants, matchWinner.id);
} else {
await db.update(match).set({ state: MATCH_STATES.PICKING }).where(eq(match.id, game.matchId));
}
return getMatchFull(game.matchId);
}
async function updateElo(participants, winnerId) {
for (const p of participants) {
const players = await db.query.matchParticipantPlayer.findMany({
where: eq(matchParticipantPlayer.participantId, p.id)
});
const isWinner = p.id === winnerId;
for (const player of players) {
let rating = await db.query.playerRating.findFirst({
where: eq(playerRating.userId, player.userId)
});
if (!rating) {
[rating] = await db.insert(playerRating).values({ userId: player.userId, elo: 1e3 }).returning();
}
const K = 32;
const expected = 0.5;
const actual = isWinner ? 1 : 0;
const newElo = Math.round(rating.elo + K * (actual - expected));
await db.update(playerRating).set({
elo: newElo,
wins: isWinner ? rating.wins + 1 : rating.wins,
losses: isWinner ? rating.losses : rating.losses + 1,
updatedAt: /* @__PURE__ */ new Date()
}).where(eq(playerRating.userId, player.userId));
}
}
}
async function cancelMatch(matchId) {
await db.update(match).set({
state: MATCH_STATES.CANCELLED,
finishedAt: /* @__PURE__ */ new Date()
}).where(eq(match.id, matchId));
return getMatchFull(matchId);
}
function getExpectedPicker(participants, gameCount) {
const sorted = [...participants].sort((a, b) => (a.pickOrder ?? 99) - (b.pickOrder ?? 99));
const pickerIndex = gameCount % sorted.length;
return sorted[pickerIndex];
}
async function getMatchFull(matchId) {
const m = await db.query.match.findFirst({
where: eq(match.id, matchId),
with: {
mappool: {
with: { slots: true }
},
participants: {
with: {
team: true,
players: true
}
},
games: {
with: {
slot: true,
scores: {
with: { player: true }
}
},
orderBy: (g, { asc: asc2 }) => [asc2(g.gameNumber)]
}
}
});
if (!m) throw new Error("Match not found");
return m;
}
async function getMatchOrThrow(matchId) {
const m = await db.query.match.findFirst({
where: eq(match.id, matchId)
});
if (!m) throw new Error("Match not found");
return m;
}
function assertState(current, expected) {
if (current !== expected) {
throw new Error(`Match is ${current}, expected ${expected}`);
}
}
export {
MATCH_STATES as M,
cancelMatch as a,
moveToRolling as b,
createMatch as c,
submitGameScores as d,
getQueueStatus as e,
getMatchFull as g,
joinQueue as j,
leaveQueue as l,
moveToLobby as m,
pickMap as p,
submitRoll as s
};

View File

@@ -0,0 +1,14 @@
let building = false;
let prerendering = false;
function set_building() {
building = true;
}
function set_prerendering() {
prerendering = true;
}
export {
set_prerendering as a,
building as b,
prerendering as p,
set_building as s
};

View File

@@ -0,0 +1,70 @@
function validator(expected) {
function validate(module, file) {
if (!module) return;
for (const key in module) {
if (key[0] === "_" || expected.has(key)) continue;
const values = [...expected.values()];
const hint = hint_for_supported_files(key, file?.slice(file.lastIndexOf("."))) ?? `valid exports are ${values.join(", ")}, or anything with a '_' prefix`;
throw new Error(`Invalid export '${key}'${file ? ` in ${file}` : ""} (${hint})`);
}
}
return validate;
}
function hint_for_supported_files(key, ext = ".js") {
const supported_files = [];
if (valid_layout_exports.has(key)) {
supported_files.push(`+layout${ext}`);
}
if (valid_page_exports.has(key)) {
supported_files.push(`+page${ext}`);
}
if (valid_layout_server_exports.has(key)) {
supported_files.push(`+layout.server${ext}`);
}
if (valid_page_server_exports.has(key)) {
supported_files.push(`+page.server${ext}`);
}
if (valid_server_exports.has(key)) {
supported_files.push(`+server${ext}`);
}
if (supported_files.length > 0) {
return `'${key}' is a valid export in ${supported_files.slice(0, -1).join(", ")}${supported_files.length > 1 ? " or " : ""}${supported_files.at(-1)}`;
}
}
const valid_layout_exports = /* @__PURE__ */ new Set([
"load",
"prerender",
"csr",
"ssr",
"trailingSlash",
"config"
]);
const valid_page_exports = /* @__PURE__ */ new Set([...valid_layout_exports, "entries"]);
const valid_layout_server_exports = /* @__PURE__ */ new Set([...valid_layout_exports]);
const valid_page_server_exports = /* @__PURE__ */ new Set([...valid_layout_server_exports, "actions", "entries"]);
const valid_server_exports = /* @__PURE__ */ new Set([
"GET",
"POST",
"PATCH",
"PUT",
"DELETE",
"OPTIONS",
"HEAD",
"fallback",
"prerender",
"trailingSlash",
"config",
"entries"
]);
const validate_layout_exports = validator(valid_layout_exports);
const validate_page_exports = validator(valid_page_exports);
const validate_layout_server_exports = validator(valid_layout_server_exports);
const validate_page_server_exports = validator(valid_page_server_exports);
const validate_server_exports = validator(valid_server_exports);
export {
validate_layout_exports as a,
validate_page_server_exports as b,
validate_page_exports as c,
validate_server_exports as d,
validate_layout_server_exports as v
};

View File

@@ -0,0 +1,4 @@
const DEV = false;
export {
DEV as D
};

View File

@@ -0,0 +1,60 @@
import { n as noop } from "./index3.js";
import { s as safe_not_equal } from "./root.js";
import "clsx";
const subscriber_queue = [];
function readable(value, start) {
return {
subscribe: writable(value, start).subscribe
};
}
function writable(value, start = noop) {
let stop = null;
const subscribers = /* @__PURE__ */ new Set();
function set(new_value) {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) {
const run_queue = !subscriber_queue.length;
for (const subscriber of subscribers) {
subscriber[1]();
subscriber_queue.push(subscriber, value);
}
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
function update(fn) {
set(fn(
/** @type {T} */
value
));
}
function subscribe(run, invalidate = noop) {
const subscriber = [run, invalidate];
subscribers.add(subscriber);
if (subscribers.size === 1) {
stop = start(set, update) || noop;
}
run(
/** @type {T} */
value
);
return () => {
subscribers.delete(subscriber);
if (subscribers.size === 0 && stop) {
stop();
stop = null;
}
};
}
return { set, update, subscribe };
}
export {
readable as r,
writable as w
};

View File

@@ -0,0 +1,302 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { pgTable, timestamp, text, boolean, index, uuid, real, integer, jsonb, uniqueIndex } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
import { b as private_env } from "./shared-server.js";
const user = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
// ── Staff role: 'player' | 'referee' | 'admin' ──
role: text("role").default("player").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()).notNull()
});
const session = pgTable(
"session",
{
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").$onUpdate(() => /* @__PURE__ */ new Date()).notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" })
},
(table) => [index("session_userId_idx").on(table.userId)]
);
const account = pgTable(
"account",
{
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at"),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").$onUpdate(() => /* @__PURE__ */ new Date()).notNull()
},
(table) => [index("account_userId_idx").on(table.userId)]
);
const verification = pgTable(
"verification",
{
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().$onUpdate(() => /* @__PURE__ */ new Date()).notNull()
},
(table) => [index("verification_identifier_idx").on(table.identifier)]
);
const userRelations = relations(user, ({ many }) => ({
sessions: many(session),
accounts: many(account)
}));
const sessionRelations = relations(session, ({ one }) => ({
user: one(user, {
fields: [session.userId],
references: [user.id]
})
}));
const accountRelations = relations(account, ({ one }) => ({
user: one(user, {
fields: [account.userId],
references: [user.id]
})
}));
const team = pgTable("team", {
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull(),
isPersonal: boolean("is_personal").default(false).notNull(),
ownerId: text("owner_id"),
avatarUrl: text("avatar_url"),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
});
const teamMember = pgTable("team_member", {
id: uuid("id").primaryKey().defaultRandom(),
teamId: uuid("team_id").notNull().references(() => team.id, { onDelete: "cascade" }),
userId: text("user_id").notNull(),
role: text("role").default("member"),
joinedAt: timestamp("joined_at", { withTimezone: true }).defaultNow()
});
const mappool = pgTable("mappool", {
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull(),
createdBy: text("created_by"),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
});
const mappoolSlot = pgTable("mappool_slot", {
id: uuid("id").primaryKey().defaultRandom(),
mappoolId: uuid("mappool_id").notNull().references(() => mappool.id, { onDelete: "cascade" }),
category: text("category").notNull(),
orderInCategory: integer("order_in_category").notNull(),
beatmapId: text("beatmap_id").notNull(),
starRating: real("star_rating"),
mods: text("mods").array().default([]).notNull()
});
const match = pgTable(
"match",
{
id: uuid("id").primaryKey().defaultRandom(),
name: text("name"),
state: text("state").default("CREATED").notNull(),
config: jsonb("config").notNull(),
mappoolId: uuid("mappool_id").references(() => mappool.id),
osuLobbyId: integer("osu_lobby_id"),
scheduledAt: timestamp("scheduled_at", { withTimezone: true }),
startedAt: timestamp("started_at", { withTimezone: true }),
finishedAt: timestamp("finished_at", { withTimezone: true }),
winnerId: uuid("winner_id").references(() => team.id),
createdBy: text("created_by"),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull()
},
(t) => [index("idx_match_state").on(t.state)]
);
const matchParticipant = pgTable(
"match_participant",
{
id: uuid("id").primaryKey().defaultRandom(),
matchId: uuid("match_id").notNull().references(() => match.id, { onDelete: "cascade" }),
teamId: uuid("team_id").notNull().references(() => team.id),
slot: integer("slot").notNull(),
score: integer("score").default(0).notNull(),
rollValue: integer("roll_value"),
pickOrder: integer("pick_order")
},
(t) => [
index("idx_mp_match_team").on(t.matchId, t.teamId),
uniqueIndex("idx_mp_match_slot").on(t.matchId, t.slot),
index("idx_mp_match").on(t.matchId)
]
);
const matchParticipantPlayer = pgTable(
"match_participant_player",
{
id: uuid("id").primaryKey().defaultRandom(),
participantId: uuid("participant_id").notNull().references(() => matchParticipant.id, { onDelete: "cascade" }),
userId: text("user_id").notNull()
},
(t) => [
uniqueIndex("idx_mpp_participant_user").on(t.participantId, t.userId),
index("idx_mpp_participant").on(t.participantId)
]
);
const matchGame = pgTable(
"match_game",
{
id: uuid("id").primaryKey().defaultRandom(),
matchId: uuid("match_id").notNull().references(() => match.id, { onDelete: "cascade" }),
gameNumber: integer("game_number").notNull(),
mappoolSlotId: uuid("mappool_slot_id").notNull().references(() => mappoolSlot.id),
pickedByParticipantId: uuid("picked_by_participant_id").references(() => matchParticipant.id),
winnerParticipantId: uuid("winner_participant_id").references(() => matchParticipant.id),
state: text("state").default("PENDING").notNull(),
startedAt: timestamp("started_at", { withTimezone: true }),
finishedAt: timestamp("finished_at", { withTimezone: true })
},
(t) => [index("idx_match_game_match").on(t.matchId)]
);
const matchGameScore = pgTable(
"match_game_score",
{
id: uuid("id").primaryKey().defaultRandom(),
matchGameId: uuid("match_game_id").notNull().references(() => matchGame.id, { onDelete: "cascade" }),
playerId: uuid("player_id").notNull().references(() => matchParticipantPlayer.id),
score: integer("score").default(0).notNull(),
accuracy: real("accuracy").default(0).notNull(),
maxCombo: integer("max_combo").default(0).notNull(),
count300: integer("count_300").default(0).notNull(),
count100: integer("count_100").default(0).notNull(),
count50: integer("count_50").default(0).notNull(),
countMiss: integer("count_miss").default(0).notNull(),
mods: text("mods").array().default([]).notNull(),
passed: boolean("passed").default(false).notNull()
},
(t) => [index("idx_mgs_game").on(t.matchGameId)]
);
const matchQueue = pgTable(
"match_queue",
{
id: uuid("id").primaryKey().defaultRandom(),
userId: text("user_id").notNull(),
teamId: uuid("team_id").notNull().references(() => team.id),
elo: integer("elo").default(1e3).notNull(),
joinedAt: timestamp("joined_at", { withTimezone: true }).defaultNow().notNull()
},
(t) => [
uniqueIndex("idx_queue_user").on(t.userId),
index("idx_queue_elo").on(t.elo)
]
);
const playerRating = pgTable("player_rating", {
id: uuid("id").primaryKey().defaultRandom(),
userId: text("user_id").notNull().unique(),
elo: integer("elo").default(1e3).notNull(),
wins: integer("wins").default(0).notNull(),
losses: integer("losses").default(0).notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
});
const teamRelations = relations(team, ({ many }) => ({
members: many(teamMember)
}));
const teamMemberRelations = relations(teamMember, ({ one }) => ({
team: one(team, { fields: [teamMember.teamId], references: [team.id] })
}));
const mappoolRelations = relations(mappool, ({ many }) => ({
slots: many(mappoolSlot)
}));
const mappoolSlotRelations = relations(mappoolSlot, ({ one }) => ({
mappool: one(mappool, { fields: [mappoolSlot.mappoolId], references: [mappool.id] })
}));
const matchRelations = relations(match, ({ one, many }) => ({
mappool: one(mappool, { fields: [match.mappoolId], references: [mappool.id] }),
participants: many(matchParticipant),
games: many(matchGame)
}));
const matchParticipantRelations = relations(matchParticipant, ({ one, many }) => ({
match: one(match, { fields: [matchParticipant.matchId], references: [match.id] }),
team: one(team, { fields: [matchParticipant.teamId], references: [team.id] }),
players: many(matchParticipantPlayer)
}));
const matchParticipantPlayerRelations = relations(matchParticipantPlayer, ({ one }) => ({
participant: one(matchParticipant, {
fields: [matchParticipantPlayer.participantId],
references: [matchParticipant.id]
})
}));
const matchGameRelations = relations(matchGame, ({ one, many }) => ({
match: one(match, { fields: [matchGame.matchId], references: [match.id] }),
slot: one(mappoolSlot, { fields: [matchGame.mappoolSlotId], references: [mappoolSlot.id] }),
scores: many(matchGameScore)
}));
const matchGameScoreRelations = relations(matchGameScore, ({ one }) => ({
game: one(matchGame, { fields: [matchGameScore.matchGameId], references: [matchGame.id] }),
player: one(matchParticipantPlayer, {
fields: [matchGameScore.playerId],
references: [matchParticipantPlayer.id]
})
}));
const matchQueueRelations = relations(matchQueue, ({ one }) => ({
team: one(team, { fields: [matchQueue.teamId], references: [team.id] })
}));
const schema = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
account,
accountRelations,
mappool,
mappoolRelations,
mappoolSlot,
mappoolSlotRelations,
match,
matchGame,
matchGameRelations,
matchGameScore,
matchGameScoreRelations,
matchParticipant,
matchParticipantPlayer,
matchParticipantPlayerRelations,
matchParticipantRelations,
matchQueue,
matchQueueRelations,
matchRelations,
playerRating,
session,
sessionRelations,
team,
teamMember,
teamMemberRelations,
teamRelations,
user,
userRelations,
verification
}, Symbol.toStringTag, { value: "Module" }));
if (!private_env.DATABASE_URL) throw new Error("DATABASE_URL is not set");
const client = postgres(private_env.DATABASE_URL);
const db = drizzle(client, { schema });
export {
mappoolSlot as a,
match as b,
team as c,
db as d,
matchParticipant as e,
matchParticipantPlayer as f,
matchGame as g,
matchGameScore as h,
matchQueue as i,
account as j,
mappool as m,
playerRating as p,
teamMember as t,
user as u
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
import "./state.svelte.js";
import "clsx";
import "@sveltejs/kit/internal";
import "./url.js";
import "./utils.js";
import { w as writable } from "./index.js";
import "@sveltejs/kit/internal/server";
import "./root.js";
import "./exports.js";
import { g as getContext } from "./index3.js";
function create_updated_store() {
const { set, subscribe } = writable(false);
{
return {
subscribe,
// eslint-disable-next-line @typescript-eslint/require-await
check: async () => false
};
}
}
const stores = {
updated: /* @__PURE__ */ create_updated_store()
};
({
check: stores.updated.check
});
function context() {
return getContext("__request__");
}
const page$1 = {
get data() {
return context().page.data;
},
get error() {
return context().page.error;
},
get status() {
return context().page.status;
},
get url() {
return context().page.url;
}
};
const page = page$1;
export {
page as p
};

View File

@@ -0,0 +1,224 @@
import { logger } from "@better-auth/core/env";
import { createAdapterFactory } from "@better-auth/core/db/adapter";
const memoryAdapter = (db, config) => {
let lazyOptions = null;
const adapterCreator = createAdapterFactory({
config: {
adapterId: "memory",
adapterName: "Memory Adapter",
usePlural: false,
debugLogs: config?.debugLogs || false,
supportsArrays: true,
customTransformInput(props) {
if ((props.options.advanced?.database?.useNumberId || props.options.advanced?.database?.generateId === "serial") && props.field === "id" && props.action === "create") return db[props.model].length + 1;
return props.data;
},
transaction: async (cb) => {
const clone = structuredClone(db);
try {
return await cb(adapterCreator(lazyOptions));
} catch (error) {
Object.keys(db).forEach((key) => {
db[key] = clone[key];
});
throw error;
}
}
},
adapter: ({ getFieldName, getDefaultFieldName, options, getModelName }) => {
const applySortToRecords = (records, sortBy, model) => {
if (!sortBy) return records;
return records.sort((a, b) => {
const field = getFieldName({
model,
field: sortBy.field
});
const aValue = a[field];
const bValue = b[field];
let comparison = 0;
if (aValue == null && bValue == null) comparison = 0;
else if (aValue == null) comparison = -1;
else if (bValue == null) comparison = 1;
else if (typeof aValue === "string" && typeof bValue === "string") comparison = aValue.localeCompare(bValue);
else if (aValue instanceof Date && bValue instanceof Date) comparison = aValue.getTime() - bValue.getTime();
else if (typeof aValue === "number" && typeof bValue === "number") comparison = aValue - bValue;
else if (typeof aValue === "boolean" && typeof bValue === "boolean") comparison = aValue === bValue ? 0 : aValue ? 1 : -1;
else comparison = String(aValue).localeCompare(String(bValue));
return sortBy.direction === "asc" ? comparison : -comparison;
});
};
function convertWhereClause(where, model, join, select) {
const baseRecords = (() => {
const table = db[model];
if (!table) {
logger.error(`[MemoryAdapter] Model ${model} not found in the DB`, Object.keys(db));
throw new Error(`Model ${model} not found`);
}
const evalClause = (record, clause) => {
const { field, value, operator } = clause;
switch (operator) {
case "in":
if (!Array.isArray(value)) throw new Error("Value must be an array");
return value.includes(record[field]);
case "not_in":
if (!Array.isArray(value)) throw new Error("Value must be an array");
return !value.includes(record[field]);
case "contains":
return record[field].includes(value);
case "starts_with":
return record[field].startsWith(value);
case "ends_with":
return record[field].endsWith(value);
case "ne":
return record[field] !== value;
case "gt":
return value != null && Boolean(record[field] > value);
case "gte":
return value != null && Boolean(record[field] >= value);
case "lt":
return value != null && Boolean(record[field] < value);
case "lte":
return value != null && Boolean(record[field] <= value);
default:
return record[field] === value;
}
};
let records = table.filter((record) => {
if (!where.length || where.length === 0) return true;
let result = evalClause(record, where[0]);
for (const clause of where) {
const clauseResult = evalClause(record, clause);
if (clause.connector === "OR") result = result || clauseResult;
else result = result && clauseResult;
}
return result;
});
if (select?.length && select.length > 0) records = records.map((record) => Object.fromEntries(Object.entries(record).filter(([key]) => select.includes(getDefaultFieldName({
model,
field: key
})))));
return records;
})();
if (!join) return baseRecords;
const grouped = /* @__PURE__ */ new Map();
const seenIds = /* @__PURE__ */ new Map();
for (const baseRecord of baseRecords) {
const baseId = String(baseRecord.id);
if (!grouped.has(baseId)) {
const nested = { ...baseRecord };
for (const [joinModel, joinAttr] of Object.entries(join)) {
const joinModelName = getModelName(joinModel);
if (joinAttr.relation === "one-to-one") nested[joinModelName] = null;
else {
nested[joinModelName] = [];
seenIds.set(`${baseId}-${joinModel}`, /* @__PURE__ */ new Set());
}
}
grouped.set(baseId, nested);
}
const nestedEntry = grouped.get(baseId);
for (const [joinModel, joinAttr] of Object.entries(join)) {
const joinModelName = getModelName(joinModel);
const joinTable = db[joinModelName];
if (!joinTable) {
logger.error(`[MemoryAdapter] JoinOption model ${joinModelName} not found in the DB`, Object.keys(db));
throw new Error(`JoinOption model ${joinModelName} not found`);
}
const matchingRecords = joinTable.filter((joinRecord) => joinRecord[joinAttr.on.to] === baseRecord[joinAttr.on.from]);
if (joinAttr.relation === "one-to-one") nestedEntry[joinModelName] = matchingRecords[0] || null;
else {
const seenSet = seenIds.get(`${baseId}-${joinModel}`);
const limit = joinAttr.limit ?? 100;
let count = 0;
for (const matchingRecord of matchingRecords) {
if (count >= limit) break;
if (!seenSet.has(matchingRecord.id)) {
nestedEntry[joinModelName].push(matchingRecord);
seenSet.add(matchingRecord.id);
count++;
}
}
}
}
}
return Array.from(grouped.values());
}
return {
create: async ({ model, data }) => {
if (options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial") data.id = db[getModelName(model)].length + 1;
if (!db[model]) db[model] = [];
db[model].push(data);
return data;
},
findOne: async ({ model, where, select, join }) => {
const res = convertWhereClause(where, model, join, select);
if (join) {
const resArray = res;
if (!resArray.length) return null;
return resArray[0];
}
return res[0] || null;
},
findMany: async ({ model, where, sortBy, limit, select, offset, join }) => {
const res = convertWhereClause(where || [], model, join, select);
if (join) {
const resArray = res;
if (!resArray.length) return [];
applySortToRecords(resArray, sortBy, model);
let paginatedRecords = resArray;
if (offset !== void 0) paginatedRecords = paginatedRecords.slice(offset);
if (limit !== void 0) paginatedRecords = paginatedRecords.slice(0, limit);
return paginatedRecords;
}
let table = applySortToRecords(res, sortBy, model);
if (offset !== void 0) table = table.slice(offset);
if (limit !== void 0) table = table.slice(0, limit);
return table || [];
},
count: async ({ model, where }) => {
if (where) return convertWhereClause(where, model).length;
return db[model].length;
},
update: async ({ model, where, update }) => {
const res = convertWhereClause(where, model);
res.forEach((record) => {
Object.assign(record, update);
});
return res[0] || null;
},
delete: async ({ model, where }) => {
const table = db[model];
const res = convertWhereClause(where, model);
db[model] = table.filter((record) => !res.includes(record));
},
deleteMany: async ({ model, where }) => {
const table = db[model];
const res = convertWhereClause(where, model);
let count = 0;
db[model] = table.filter((record) => {
if (res.includes(record)) {
count++;
return false;
}
return !res.includes(record);
});
return count;
},
updateMany({ model, where, update }) {
const res = convertWhereClause(where, model);
res.forEach((record) => {
Object.assign(record, update);
});
return res[0] || null;
}
};
}
});
return (options) => {
lazyOptions = options;
return adapterCreator(options);
};
};
export {
memoryAdapter
};

View File

@@ -0,0 +1,307 @@
import { c as createKyselyAdapter } from "./auth.js";
import { g } from "./auth.js";
import { sql } from "kysely";
import { createAdapterFactory } from "@better-auth/core/db/adapter";
const kyselyAdapter = (db, config) => {
let lazyOptions = null;
const createCustomAdapter = (db$1) => {
return ({ getFieldName, schema, getDefaultFieldName, getDefaultModelName, getFieldAttributes, getModelName }) => {
const selectAllJoins = (join) => {
const allSelects = [];
const allSelectsStr = [];
if (join) for (const [joinModel, _] of Object.entries(join)) {
const fields = schema[getDefaultModelName(joinModel)]?.fields;
const [_joinModelSchema, joinModelName] = joinModel.includes(".") ? joinModel.split(".") : [void 0, joinModel];
if (!fields) continue;
fields.id = { type: "string" };
for (const [field, fieldAttr] of Object.entries(fields)) {
allSelects.push(sql`${sql.ref(`join_${joinModelName}`)}.${sql.ref(fieldAttr.fieldName || field)} as ${sql.ref(`_joined_${joinModelName}_${fieldAttr.fieldName || field}`)}`);
allSelectsStr.push({
joinModel,
joinModelRef: joinModelName,
fieldName: fieldAttr.fieldName || field
});
}
}
return {
allSelectsStr,
allSelects
};
};
const withReturning = async (values, builder, model, where) => {
let res;
if (config?.type === "mysql") {
await builder.execute();
const field = values.id ? "id" : where.length > 0 && where[0]?.field ? where[0].field : "id";
if (!values.id && where.length === 0) {
res = await db$1.selectFrom(model).selectAll().orderBy(getFieldName({
model,
field
}), "desc").limit(1).executeTakeFirst();
return res;
}
const value = values[field] || where[0]?.value;
res = await db$1.selectFrom(model).selectAll().orderBy(getFieldName({
model,
field
}), "desc").where(getFieldName({
model,
field
}), "=", value).limit(1).executeTakeFirst();
return res;
}
if (config?.type === "mssql") {
res = await builder.outputAll("inserted").executeTakeFirst();
return res;
}
res = await builder.returningAll().executeTakeFirst();
return res;
};
function convertWhereClause(model, w) {
if (!w) return {
and: null,
or: null
};
const conditions = {
and: [],
or: []
};
w.forEach((condition) => {
const { field: _field, value: _value, operator = "=", connector = "AND" } = condition;
const value = _value;
const field = getFieldName({
model,
field: _field
});
const expr = (eb) => {
const f = `${model}.${field}`;
if (operator.toLowerCase() === "in") return eb(f, "in", Array.isArray(value) ? value : [value]);
if (operator.toLowerCase() === "not_in") return eb(f, "not in", Array.isArray(value) ? value : [value]);
if (operator === "contains") return eb(f, "like", `%${value}%`);
if (operator === "starts_with") return eb(f, "like", `${value}%`);
if (operator === "ends_with") return eb(f, "like", `%${value}`);
if (operator === "eq") return eb(f, "=", value);
if (operator === "ne") return eb(f, "<>", value);
if (operator === "gt") return eb(f, ">", value);
if (operator === "gte") return eb(f, ">=", value);
if (operator === "lt") return eb(f, "<", value);
if (operator === "lte") return eb(f, "<=", value);
return eb(f, operator, value);
};
if (connector === "OR") conditions.or.push(expr);
else conditions.and.push(expr);
});
return {
and: conditions.and.length ? conditions.and : null,
or: conditions.or.length ? conditions.or : null
};
}
function processJoinedResults(rows, joinConfig, allSelectsStr) {
if (!joinConfig || !rows.length) return rows;
const groupedByMainId = /* @__PURE__ */ new Map();
for (const currentRow of rows) {
const mainModelFields = {};
const joinedModelFields = {};
for (const [joinModel] of Object.entries(joinConfig)) joinedModelFields[getModelName(joinModel)] = {};
for (const [key, value] of Object.entries(currentRow)) {
const keyStr = String(key);
let assigned = false;
for (const { joinModel, fieldName, joinModelRef } of allSelectsStr) if (keyStr === `_joined_${joinModelRef}_${fieldName}`) {
joinedModelFields[getModelName(joinModel)][getFieldName({
model: joinModel,
field: fieldName
})] = value;
assigned = true;
break;
}
if (!assigned) mainModelFields[key] = value;
}
const mainId = mainModelFields.id;
if (!mainId) continue;
if (!groupedByMainId.has(mainId)) {
const entry$1 = { ...mainModelFields };
for (const [joinModel, joinAttr] of Object.entries(joinConfig)) entry$1[getModelName(joinModel)] = joinAttr.relation === "one-to-one" ? null : [];
groupedByMainId.set(mainId, entry$1);
}
const entry = groupedByMainId.get(mainId);
for (const [joinModel, joinAttr] of Object.entries(joinConfig)) {
const isUnique = joinAttr.relation === "one-to-one";
const limit = joinAttr.limit ?? 100;
const joinedObj = joinedModelFields[getModelName(joinModel)];
const hasData = joinedObj && Object.keys(joinedObj).length > 0 && Object.values(joinedObj).some((value) => value !== null && value !== void 0);
if (isUnique) entry[getModelName(joinModel)] = hasData ? joinedObj : null;
else {
const joinModelName = getModelName(joinModel);
if (Array.isArray(entry[joinModelName]) && hasData) {
if (entry[joinModelName].length >= limit) continue;
const idFieldName = getFieldName({
model: joinModel,
field: "id"
});
const joinedId = joinedObj[idFieldName];
if (joinedId) {
if (!entry[joinModelName].some((item) => item[idFieldName] === joinedId) && entry[joinModelName].length < limit) entry[joinModelName].push(joinedObj);
} else if (entry[joinModelName].length < limit) entry[joinModelName].push(joinedObj);
}
}
}
}
const result = Array.from(groupedByMainId.values());
for (const entry of result) for (const [joinModel, joinAttr] of Object.entries(joinConfig)) if (joinAttr.relation !== "one-to-one") {
const joinModelName = getModelName(joinModel);
if (Array.isArray(entry[joinModelName])) {
const limit = joinAttr.limit ?? 100;
if (entry[joinModelName].length > limit) entry[joinModelName] = entry[joinModelName].slice(0, limit);
}
}
return result;
}
return {
async create({ data, model }) {
return await withReturning(data, db$1.insertInto(model).values(data), model, []);
},
async findOne({ model, where, select, join }) {
const { and, or } = convertWhereClause(model, where);
let query = db$1.selectFrom((eb) => {
let b = eb.selectFrom(model);
if (and) b = b.where((eb$1) => eb$1.and(and.map((expr) => expr(eb$1))));
if (or) b = b.where((eb$1) => eb$1.or(or.map((expr) => expr(eb$1))));
if (select?.length && select.length > 0) b = b.select(select.map((field) => getFieldName({
model,
field
})));
else b = b.selectAll();
return b.as("primary");
}).selectAll("primary");
if (join) for (const [joinModel, joinAttr] of Object.entries(join)) {
const [_joinModelSchema, joinModelName] = joinModel.includes(".") ? joinModel.split(".") : [void 0, joinModel];
query = query.leftJoin(`${joinModel} as join_${joinModelName}`, (join$1) => join$1.onRef(`join_${joinModelName}.${joinAttr.on.to}`, "=", `primary.${joinAttr.on.from}`));
}
const { allSelectsStr, allSelects } = selectAllJoins(join);
query = query.select(allSelects);
const res = await query.execute();
if (!res || !Array.isArray(res) || res.length === 0) return null;
const row = res[0];
if (join) return processJoinedResults(res, join, allSelectsStr)[0];
return row;
},
async findMany({ model, where, limit, select, offset, sortBy, join }) {
const { and, or } = convertWhereClause(model, where);
let query = db$1.selectFrom((eb) => {
let b = eb.selectFrom(model);
if (config?.type === "mssql") {
if (offset !== void 0) {
if (!sortBy) b = b.orderBy(getFieldName({
model,
field: "id"
}));
b = b.offset(offset).fetch(limit || 100);
} else if (limit !== void 0) b = b.top(limit);
} else {
if (limit !== void 0) b = b.limit(limit);
if (offset !== void 0) b = b.offset(offset);
}
if (sortBy?.field) b = b.orderBy(`${getFieldName({
model,
field: sortBy.field
})}`, sortBy.direction);
if (and) b = b.where((eb$1) => eb$1.and(and.map((expr) => expr(eb$1))));
if (or) b = b.where((eb$1) => eb$1.or(or.map((expr) => expr(eb$1))));
if (select?.length && select.length > 0) b = b.select(select.map((field) => getFieldName({
model,
field
})));
else b = b.selectAll();
return b.as("primary");
}).selectAll("primary");
if (join) for (const [joinModel, joinAttr] of Object.entries(join)) {
const [_joinModelSchema, joinModelName] = joinModel.includes(".") ? joinModel.split(".") : [void 0, joinModel];
query = query.leftJoin(`${joinModel} as join_${joinModelName}`, (join$1) => join$1.onRef(`join_${joinModelName}.${joinAttr.on.to}`, "=", `primary.${joinAttr.on.from}`));
}
const { allSelectsStr, allSelects } = selectAllJoins(join);
query = query.select(allSelects);
if (sortBy?.field) query = query.orderBy(`${getFieldName({
model,
field: sortBy.field
})}`, sortBy.direction);
const res = await query.execute();
if (!res) return [];
if (join) return processJoinedResults(res, join, allSelectsStr);
return res;
},
async update({ model, where, update: values }) {
const { and, or } = convertWhereClause(model, where);
let query = db$1.updateTable(model).set(values);
if (and) query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
if (or) query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
return await withReturning(values, query, model, where);
},
async updateMany({ model, where, update: values }) {
const { and, or } = convertWhereClause(model, where);
let query = db$1.updateTable(model).set(values);
if (and) query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
if (or) query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
const res = (await query.executeTakeFirst()).numUpdatedRows;
return res > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : Number(res);
},
async count({ model, where }) {
const { and, or } = convertWhereClause(model, where);
let query = db$1.selectFrom(model).select(db$1.fn.count("id").as("count"));
if (and) query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
if (or) query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
const res = await query.execute();
if (typeof res[0].count === "number") return res[0].count;
if (typeof res[0].count === "bigint") return Number(res[0].count);
return parseInt(res[0].count);
},
async delete({ model, where }) {
const { and, or } = convertWhereClause(model, where);
let query = db$1.deleteFrom(model);
if (and) query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
if (or) query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
await query.execute();
},
async deleteMany({ model, where }) {
const { and, or } = convertWhereClause(model, where);
let query = db$1.deleteFrom(model);
if (and) query = query.where((eb) => eb.and(and.map((expr) => expr(eb))));
if (or) query = query.where((eb) => eb.or(or.map((expr) => expr(eb))));
const res = (await query.executeTakeFirst()).numDeletedRows;
return res > Number.MAX_SAFE_INTEGER ? Number.MAX_SAFE_INTEGER : Number(res);
},
options: config
};
};
};
let adapterOptions = null;
adapterOptions = {
config: {
adapterId: "kysely",
adapterName: "Kysely Adapter",
usePlural: config?.usePlural,
debugLogs: config?.debugLogs,
supportsBooleans: config?.type === "sqlite" || config?.type === "mssql" || config?.type === "mysql" || !config?.type ? false : true,
supportsDates: config?.type === "sqlite" || config?.type === "mssql" || !config?.type ? false : true,
supportsJSON: config?.type === "postgres" ? true : false,
supportsArrays: false,
supportsUUIDs: config?.type === "postgres" ? true : false,
transaction: config?.transaction ? (cb) => db.transaction().execute((trx) => {
return cb(createAdapterFactory({
config: adapterOptions.config,
adapter: createCustomAdapter(trx)
})(lazyOptions));
}) : false
},
adapter: createCustomAdapter(db)
};
const adapter = createAdapterFactory(adapterOptions);
return (options) => {
lazyOptions = options;
return adapter(options);
};
};
export {
createKyselyAdapter,
g as getKyselyDatabaseType,
kyselyAdapter
};

View File

@@ -0,0 +1,127 @@
import { r as root } from "./root.js";
import "./environment.js";
import "./server.js";
import "./shared-server.js";
let read_implementation = null;
function set_read_implementation(fn) {
read_implementation = fn;
}
function set_manifest(_) {
}
const options = {
app_template_contains_nonce: false,
async: false,
csp: { "mode": "auto", "directives": { "upgrade-insecure-requests": false, "block-all-mixed-content": false }, "reportOnly": { "upgrade-insecure-requests": false, "block-all-mixed-content": false } },
csrf_check_origin: true,
csrf_trusted_origins: [],
embedded: false,
env_public_prefix: "PUBLIC_",
env_private_prefix: "",
hash_routing: false,
hooks: null,
// added lazily, via `get_hooks`
preload_strategy: "modulepreload",
root,
service_worker: false,
service_worker_options: void 0,
templates: {
app: ({ head, body, assets, nonce, env }) => '<!doctype html>\n<html lang="en">\n <head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n <link rel="preconnect" href="https://fonts.googleapis.com" />\n <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />\n <link\n href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700;800&family=Geist+Mono:wght@400;500&display=swap"\n rel="stylesheet"\n />\n <title>Vash Esports</title>\n ' + head + '\n </head>\n <body data-sveltekit-preload-data="hover">\n <div style="display: contents">' + body + "</div>\n </body>\n</html>\n",
error: ({ status, message }) => '<!doctype html>\n<html lang="en">\n <head>\n <meta charset="utf-8" />\n <title>' + message + `</title>
<style>
body {
--bg: white;
--fg: #222;
--divider: #ccc;
background: var(--bg);
color: var(--fg);
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
.error {
display: flex;
align-items: center;
max-width: 32rem;
margin: 0 1rem;
}
.status {
font-weight: 200;
font-size: 3rem;
line-height: 1;
position: relative;
top: -0.05rem;
}
.message {
border-left: 1px solid var(--divider);
padding: 0 0 0 1rem;
margin: 0 0 0 1rem;
min-height: 2.5rem;
display: flex;
align-items: center;
}
.message h1 {
font-weight: 400;
font-size: 1em;
margin: 0;
}
@media (prefers-color-scheme: dark) {
body {
--bg: #222;
--fg: #ddd;
--divider: #666;
}
}
</style>
</head>
<body>
<div class="error">
<span class="status">` + status + '</span>\n <div class="message">\n <h1>' + message + "</h1>\n </div>\n </div>\n </body>\n</html>\n"
},
version_hash: "12t8tv8"
};
async function get_hooks() {
let handle;
let handleFetch;
let handleError;
let handleValidationError;
let init;
({ handle, handleFetch, handleError, handleValidationError, init } = await import("../entries/hooks.server.js"));
let reroute;
let transport;
return {
handle,
handleFetch,
handleError,
handleValidationError,
init,
reroute,
transport
};
}
export {
set_manifest as a,
get_hooks as g,
options as o,
read_implementation as r,
set_read_implementation as s
};

View File

@@ -0,0 +1,155 @@
import { CompiledQuery, DefaultQueryCompiler, DEFAULT_MIGRATION_TABLE, DEFAULT_MIGRATION_LOCK_TABLE, sql } from "kysely";
var NodeSqliteAdapter = class {
get supportsCreateIfNotExists() {
return true;
}
get supportsTransactionalDdl() {
return false;
}
get supportsReturning() {
return true;
}
async acquireMigrationLock() {
}
async releaseMigrationLock() {
}
get supportsOutput() {
return true;
}
};
var NodeSqliteDriver = class {
#config;
#connectionMutex = new ConnectionMutex();
#db;
#connection;
constructor(config) {
this.#config = { ...config };
}
async init() {
this.#db = this.#config.database;
this.#connection = new NodeSqliteConnection(this.#db);
if (this.#config.onCreateConnection) await this.#config.onCreateConnection(this.#connection);
}
async acquireConnection() {
await this.#connectionMutex.lock();
return this.#connection;
}
async beginTransaction(connection) {
await connection.executeQuery(CompiledQuery.raw("begin"));
}
async commitTransaction(connection) {
await connection.executeQuery(CompiledQuery.raw("commit"));
}
async rollbackTransaction(connection) {
await connection.executeQuery(CompiledQuery.raw("rollback"));
}
async releaseConnection() {
this.#connectionMutex.unlock();
}
async destroy() {
this.#db?.close();
}
};
var NodeSqliteConnection = class {
#db;
constructor(db) {
this.#db = db;
}
executeQuery(compiledQuery) {
const { sql: sql$1, parameters } = compiledQuery;
const rows = this.#db.prepare(sql$1).all(...parameters);
return Promise.resolve({ rows });
}
async *streamQuery() {
throw new Error("Streaming query is not supported by SQLite driver.");
}
};
var ConnectionMutex = class {
#promise;
#resolve;
async lock() {
while (await this.#promise) await this.#promise;
this.#promise = new Promise((resolve) => {
this.#resolve = resolve;
});
}
unlock() {
const resolve = this.#resolve;
this.#promise = void 0;
this.#resolve = void 0;
resolve?.();
}
};
var NodeSqliteIntrospector = class {
#db;
constructor(db) {
this.#db = db;
}
async getSchemas() {
return [];
}
async getTables(options = { withInternalKyselyTables: false }) {
let query = this.#db.selectFrom("sqlite_schema").where("type", "=", "table").where("name", "not like", "sqlite_%").select("name").$castTo();
if (!options.withInternalKyselyTables) query = query.where("name", "!=", DEFAULT_MIGRATION_TABLE).where("name", "!=", DEFAULT_MIGRATION_LOCK_TABLE);
const tables = await query.execute();
return Promise.all(tables.map(({ name }) => this.#getTableMetadata(name)));
}
async getMetadata(options) {
return { tables: await this.getTables(options) };
}
async #getTableMetadata(table) {
const db = this.#db;
const autoIncrementCol = (await db.selectFrom("sqlite_master").where("name", "=", table).select("sql").$castTo().execute())[0]?.sql?.split(/[\(\),]/)?.find((it) => it.toLowerCase().includes("autoincrement"))?.split(/\s+/)?.[0]?.replace(/["`]/g, "");
return {
name: table,
columns: (await db.selectFrom(sql`pragma_table_info(${table})`.as("table_info")).select([
"name",
"type",
"notnull",
"dflt_value"
]).execute()).map((col) => ({
name: col.name,
dataType: col.type,
isNullable: !col.notnull,
isAutoIncrementing: col.name === autoIncrementCol,
hasDefaultValue: col.dflt_value != null
})),
isView: true
};
}
};
var NodeSqliteQueryCompiler = class extends DefaultQueryCompiler {
getCurrentParameterPlaceholder() {
return "?";
}
getLeftIdentifierWrapper() {
return '"';
}
getRightIdentifierWrapper() {
return '"';
}
getAutoIncrement() {
return "autoincrement";
}
};
var NodeSqliteDialect = class {
#config;
constructor(config) {
this.#config = { ...config };
}
createDriver() {
return new NodeSqliteDriver(this.#config);
}
createQueryCompiler() {
return new NodeSqliteQueryCompiler();
}
createAdapter() {
return new NodeSqliteAdapter();
}
createIntrospector(db) {
return new NodeSqliteIntrospector(db);
}
};
export {
NodeSqliteDialect
};

View File

@@ -0,0 +1,448 @@
import { d as db, b as match, u as user, g as matchGame } from "./index2.js";
import { eq } from "drizzle-orm";
import { g as getMatchFull, M as MATCH_STATES, s as submitRoll, p as pickMap, d as submitGameScores } from "./engine.js";
import { b as private_env } from "./shared-server.js";
let client = null;
let connecting = null;
async function getClient() {
if (client?.isConnected?.()) return client;
if (connecting) {
await connecting;
return client;
}
if (!private_env.OSU_IRC_USERNAME || !private_env.OSU_IRC_PASSWORD) {
throw new Error("OSU_IRC_USERNAME and OSU_IRC_PASSWORD must be set in .env");
}
const banchoModule = await import("bancho.js");
const BanchoClient = banchoModule.BanchoClient ?? banchoModule.default?.BanchoClient ?? banchoModule.default;
client = new BanchoClient({
username: private_env.OSU_IRC_USERNAME,
password: private_env.OSU_IRC_PASSWORD
});
connecting = client.connect().then(() => {
console.log("[Bancho] Connected as", private_env.OSU_IRC_USERNAME);
connecting = null;
});
await connecting;
return client;
}
class TournamentLobby {
channel = null;
channelName = "";
osuMatchId = 0;
// Score collection
collectedScores = [];
matchFinishedResolve = null;
// Ready tracking
allReadyResolve = null;
// Event callbacks (set by orchestrator)
onRollResult = null;
onPickCommand = null;
/**
* Create a new multiplayer lobby via !mp make.
*/
async create(name) {
const c = await getClient();
return new Promise((resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error("Lobby creation timed out")),
15e3
);
const banchoBot = c.getUser("BanchoBot");
const onPM = (msg) => {
const text = msg.message ?? String(msg);
const m = text.match(/https:\/\/osu\.ppy\.sh\/mp\/(\d+)/);
if (m) {
clearTimeout(timeout);
banchoBot.removeListener("message", onPM);
this.osuMatchId = parseInt(m[1]);
this.channelName = `#mp_${this.osuMatchId}`;
this.joinChannel(c).then(
() => resolve({ matchId: this.osuMatchId, channel: this.channelName })
);
}
};
banchoBot.on("message", onPM);
banchoBot.sendMessage(`!mp make ${name}`);
});
}
async joinChannel(c) {
this.channel = c.getChannel(this.channelName);
await this.channel.join();
this.channel.on("message", (msg) => {
const text = msg.message ?? String(msg);
const sender = msg.user?.ircUsername ?? "";
if (sender === "BanchoBot") {
this.parseBanchoMessage(text);
} else if (sender) {
this.parsePlayerMessage(sender, text);
}
});
}
// ── Message Parsing ─────────────────────────────────────────────────
parseBanchoMessage(text) {
const scoreMatch = text.match(
/^(.+?) finished playing \(Score: ([\d,]+).*?(PASSED|FAILED)\)/i
);
if (scoreMatch) {
this.collectedScores.push({
username: scoreMatch[1].trim(),
score: parseInt(scoreMatch[2].replace(/,/g, "")),
passed: scoreMatch[3] === "PASSED"
});
console.log(`[Bancho] Score: ${scoreMatch[1].trim()} = ${scoreMatch[2]}`);
return;
}
if (text.includes("The match has finished!")) {
console.log("[Bancho] Game finished. Scores:", this.collectedScores);
if (this.matchFinishedResolve) {
this.matchFinishedResolve([...this.collectedScores]);
this.matchFinishedResolve = null;
}
this.collectedScores = [];
return;
}
const rollMatch = text.match(/^(.+?) rolls (\d+) point\(s\)/);
if (rollMatch) {
const username = rollMatch[1].trim();
const value = parseInt(rollMatch[2]);
console.log(`[Bancho] Roll: ${username} = ${value}`);
if (this.onRollResult) {
this.onRollResult(username, value);
}
return;
}
if (text.includes("All players are ready")) {
console.log("[Bancho] All players ready");
if (this.allReadyResolve) {
this.allReadyResolve();
this.allReadyResolve = null;
}
return;
}
}
parsePlayerMessage(sender, text) {
const pickMatch = text.match(/^!pick\s+([A-Z]{2})(\d*)/i);
if (pickMatch) {
const category = pickMatch[1].toUpperCase();
const num = pickMatch[2] ? parseInt(pickMatch[2]) : 1;
const label = `${category}${num}`;
console.log(`[Bancho] Pick command from ${sender}: ${label}`);
if (this.onPickCommand) {
this.onPickCommand(sender, label);
}
return;
}
}
// ── IRC Commands ────────────────────────────────────────────────────
async send(cmd) {
if (!this.channel) throw new Error("Lobby not created");
console.log(`[Bancho] ${this.channelName} > ${cmd}`);
await this.channel.sendMessage(cmd);
}
async invite(username) {
await this.send(`!mp invite ${username.replace(/ /g, "_")}`);
}
async setProperties(teamMode, scoreMode, size) {
await this.send(`!mp set ${teamMode} ${scoreMode} ${size}`);
}
async setMap(beatmapId, playmode = 0) {
await this.send(`!mp map ${beatmapId} ${playmode}`);
}
async setMods(mods) {
const filtered = mods.filter((m) => !["NM", "TB", "None", "NF"].includes(m));
if (mods.includes("FM") || mods.includes("Freemod")) {
await this.send("!mp mods Freemod NF");
} else if (filtered.length > 0) {
await this.send(`!mp mods NF ${filtered.join(" ")}`);
} else {
await this.send("!mp mods NF");
}
}
async startGame(delaySec = 5) {
await this.send(`!mp start ${delaySec}`);
}
async close() {
try {
await this.send("!mp close");
} catch {
}
}
async chat(message) {
await this.send(message);
}
// ── Async Waiters ───────────────────────────────────────────────────
/**
* Wait for all players to click "Ready" in osu! client.
* BanchoBot sends "All players are ready" when they do.
*/
waitForReady(timeoutMs = 18e4) {
return new Promise((resolve, reject) => {
const t = setTimeout(() => {
this.allReadyResolve = null;
reject(new Error("Timed out waiting for players to ready"));
}, timeoutMs);
this.allReadyResolve = () => {
clearTimeout(t);
resolve();
};
});
}
/**
* Wait for "The match has finished!" and return all player scores.
*/
waitForScores(timeoutMs = 6e5) {
this.collectedScores = [];
return new Promise((resolve, reject) => {
const t = setTimeout(() => {
this.matchFinishedResolve = null;
reject(new Error("Timed out waiting for match scores"));
}, timeoutMs);
this.matchFinishedResolve = (scores) => {
clearTimeout(t);
resolve(scores);
};
});
}
}
const lobbies = /* @__PURE__ */ new Map();
function getLobby(matchId) {
return lobbies.get(matchId);
}
function setLobby(matchId, lobby) {
lobbies.set(matchId, lobby);
}
function removeLobby(matchId) {
lobbies.delete(matchId);
}
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function initMatchLobby(matchId) {
const m = await getMatchFull(matchId);
const config = m.config;
const lobby = new TournamentLobby();
const lobbyName = `VASH: (${m.participants[0]?.team.name}) vs (${m.participants[1]?.team.name})`;
console.log(`[Orchestrator] Creating lobby: ${lobbyName}`);
const { matchId: osuId, channel } = await lobby.create(lobbyName);
console.log(`[Orchestrator] Created ${channel} (osu mp/${osuId})`);
setLobby(matchId, lobby);
await db.update(match).set({ state: MATCH_STATES.LOBBY, osuLobbyId: osuId, startedAt: /* @__PURE__ */ new Date() }).where(eq(match.id, matchId));
const scoreMode = config.scoringType === "score_v2" ? 3 : config.scoringType === "accuracy" ? 1 : config.scoringType === "combo" ? 2 : 0;
await lobby.setProperties(0, scoreMode, config.teamSize * 2);
await lobby.send("!mp password");
await sleep(500);
const invited = /* @__PURE__ */ new Set();
for (const p of m.participants) {
for (const pl of p.players) {
const u = await db.query.user.findFirst({ where: eq(user.id, pl.userId) });
if (u?.name && !invited.has(u.name.toLowerCase())) {
await lobby.invite(u.name);
invited.add(u.name.toLowerCase());
console.log(`[Orchestrator] Invited ${u.name}`);
await sleep(300);
}
}
}
setupChatHandlers(matchId, lobby);
await lobby.chat(
`Welcome! BO${config.bestOf}. Type !roll to decide pick order, or use the web UI.`
);
await db.update(match).set({ state: MATCH_STATES.ROLLING }).where(eq(match.id, matchId));
return { osuMatchId: osuId, channel };
}
function setupChatHandlers(matchId, lobby) {
lobby.onRollResult = async (username, value) => {
try {
const m = await getMatchFull(matchId);
if (m.state !== MATCH_STATES.ROLLING) {
await lobby.chat(`Rolls are not active right now.`);
return;
}
let targetParticipant = null;
for (const p of m.participants) {
for (const pl of p.players) {
const u = await db.query.user.findFirst({ where: eq(user.id, pl.userId) });
if (u?.name?.toLowerCase() === username.toLowerCase().replace(/_/g, " ")) {
if (p.rollValue === null) {
targetParticipant = p;
break;
} else if (!targetParticipant) {
const unrolled = m.participants.find((pp) => pp.rollValue === null);
if (unrolled) targetParticipant = unrolled;
}
}
}
if (targetParticipant?.rollValue === null) break;
}
if (!targetParticipant || targetParticipant.rollValue !== null) {
await lobby.chat(`${username}: already rolled or not in this match.`);
return;
}
await submitRoll(matchId, targetParticipant.id, value);
console.log(`[Orchestrator] IRC roll: ${username} = ${value}`);
const updated = await getMatchFull(matchId);
if (updated.state === MATCH_STATES.PICKING) {
const sorted = [...updated.participants].sort(
(a, b) => (a.pickOrder ?? 99) - (b.pickOrder ?? 99)
);
await lobby.chat(
`Rolls complete! ${sorted[0]?.team.name} picks first. Use !pick <slot> (e.g. !pick NM1) or pick in web UI.`
);
}
} catch (err) {
console.error("[Orchestrator] IRC roll error:", err.message);
}
};
lobby.onPickCommand = async (username, slotLabel) => {
try {
const m = await getMatchFull(matchId);
if (m.state !== MATCH_STATES.PICKING) {
await lobby.chat(`Picks are not active right now.`);
return;
}
const labelMatch = slotLabel.match(/^([A-Z]{2})(\d+)$/);
if (!labelMatch) {
await lobby.chat(`${username}: Invalid slot. Use format like NM1, HD2, DT1.`);
return;
}
const category = labelMatch[1];
const order = parseInt(labelMatch[2]);
const sorted = [...m.participants].sort(
(a, b) => (a.pickOrder ?? 99) - (b.pickOrder ?? 99)
);
const expectedIdx = m.games.length % sorted.length;
const expectedPicker = sorted[expectedIdx];
let isPickerTurn = false;
if (expectedPicker) {
for (const pl of expectedPicker.players) {
const u = await db.query.user.findFirst({ where: eq(user.id, pl.userId) });
if (u?.name?.toLowerCase() === username.toLowerCase().replace(/_/g, " ")) {
isPickerTurn = true;
break;
}
}
}
if (!isPickerTurn && m.participants[0]?.teamId === m.participants[1]?.teamId) {
isPickerTurn = true;
}
if (!isPickerTurn) {
await lobby.chat(`${username}: It's not your turn to pick.`);
return;
}
const slot = m.mappool?.slots?.find(
(s) => s.category === category && s.orderInCategory === order
);
if (!slot) {
await lobby.chat(`${username}: Slot ${slotLabel} not found in this mappool.`);
return;
}
const alreadyPlayed = m.games.some((g) => g.mappoolSlotId === slot.id);
if (alreadyPlayed) {
await lobby.chat(`${username}: ${slotLabel} has already been played.`);
return;
}
const game = await pickMap(matchId, expectedPicker.id, slot.id);
console.log(`[Orchestrator] IRC pick: ${username} picked ${slotLabel}`);
playPickedMap(matchId, game.id).catch(
(err) => console.error("[Orchestrator] IRC play failed:", err.message)
);
} catch (err) {
console.error("[Orchestrator] IRC pick error:", err.message);
await lobby.chat(`Error: ${err.message}`).catch(() => {
});
}
};
}
async function playPickedMap(matchId, matchGameId) {
const lobby = getLobby(matchId);
if (!lobby) {
console.warn("[Orchestrator] No IRC lobby for", matchId, "— skipping IRC");
return;
}
const game = await db.query.matchGame.findFirst({
where: eq(matchGame.id, matchGameId),
with: { slot: true }
});
if (!game) throw new Error("Game not found");
const slot = game.slot;
await lobby.setMap(slot.beatmapId);
await sleep(1e3);
await lobby.setMods(slot.mods);
await sleep(500);
await lobby.chat(
`Playing ${slot.category}${slot.orderInCategory}. Ready up! (Game starts when all players are ready)`
);
try {
await lobby.waitForReady();
await lobby.chat("All ready — starting in 5s!");
await lobby.startGame(5);
} catch (err) {
console.warn("[Orchestrator] Ready timeout:", err.message);
await lobby.chat("Ready timed out. Use the web UI to force start, or ready up!");
return;
}
collectScores(matchId, matchGameId, lobby).catch(
(err) => console.error("[Orchestrator] Score collection failed:", err)
);
}
async function forceStartGame(matchId) {
const lobby = getLobby(matchId);
if (!lobby) return;
await lobby.chat("Force starting in 10s!");
await lobby.startGame(10);
}
async function collectScores(matchId, matchGameId, lobby) {
const ircScores = await lobby.waitForScores();
console.log(`[Orchestrator] Scores for game ${matchGameId}:`, ircScores);
const m = await getMatchFull(matchId);
const scores = [];
for (const irc of ircScores) {
for (const participant of m.participants) {
for (const player of participant.players) {
const u = await db.query.user.findFirst({ where: eq(user.id, player.userId) });
const ircName = irc.username.toLowerCase().replace(/_/g, " ");
const dbName = u?.name?.toLowerCase();
if (dbName && (dbName === ircName || dbName === irc.username.toLowerCase())) {
scores.push({ playerId: player.id, score: irc.score, passed: irc.passed });
}
}
}
}
if (scores.length === 0) {
console.warn("[Orchestrator] No scores matched any IRC usernames");
return;
}
await submitGameScores(matchGameId, scores);
const updated = await getMatchFull(matchId);
if (updated.state === MATCH_STATES.FINISHED) {
const winner = updated.participants.find((p) => p.teamId === updated.winnerId);
await lobby.chat(`GG! ${winner?.team.name ?? "?"} wins the match!`);
await sleep(5e3);
await lobby.close();
removeLobby(matchId);
} else {
const sorted = [...updated.participants].sort(
(a, b) => (a.pickOrder ?? 99) - (b.pickOrder ?? 99)
);
const nextIdx = updated.games.length % sorted.length;
const nextPicker = sorted[nextIdx];
await lobby.chat(
`${nextPicker?.team.name}'s turn to pick. Use !pick <slot> (e.g. !pick HD1) or pick in web UI.`
);
}
}
async function closeLobby(matchId) {
const lobby = getLobby(matchId);
if (lobby) {
await lobby.close();
removeLobby(matchId);
}
}
export {
closeLobby as c,
forceStartGame as f,
getLobby as g,
initMatchLobby as i,
playPickedMap as p
};

View File

@@ -0,0 +1,258 @@
import { get_request_store, with_request_store } from "@sveltejs/kit/internal/server";
import { z as stringify_remote_arg, f as create_remote_key, h as handle_error_and_jsonify } from "./shared.js";
import { p as prerendering } from "./environment.js";
import { parse } from "devalue";
import { error } from "@sveltejs/kit";
import { HttpError, SvelteKitError } from "@sveltejs/kit/internal";
function create_validator(validate_or_fn, maybe_fn) {
if (!maybe_fn) {
return (arg) => {
if (arg !== void 0) {
error(400, "Bad Request");
}
};
}
if (validate_or_fn === "unchecked") {
return (arg) => arg;
}
if ("~standard" in validate_or_fn) {
return async (arg) => {
const { event, state } = get_request_store();
const result = await validate_or_fn["~standard"].validate(arg);
if (result.issues) {
error(
400,
await state.handleValidationError({
issues: result.issues,
event
})
);
}
return result.value;
};
}
throw new Error(
'Invalid validator passed to remote function. Expected "unchecked" or a Standard Schema (https://standardschema.dev)'
);
}
async function get_response(info, arg, state, get_result) {
await 0;
const cache = get_cache(info, state);
return cache[stringify_remote_arg(arg, state.transport)] ??= get_result();
}
function parse_remote_response(data, transport) {
const revivers = {};
for (const key in transport) {
revivers[key] = transport[key].decode;
}
return parse(data, revivers);
}
async function run_remote_function(event, state, allow_cookies, get_input, fn) {
const store = {
event: {
...event,
setHeaders: () => {
throw new Error("setHeaders is not allowed in remote functions");
},
cookies: {
...event.cookies,
set: (name, value, opts) => {
if (!allow_cookies) {
throw new Error("Cannot set cookies in `query` or `prerender` functions");
}
if (opts.path && !opts.path.startsWith("/")) {
throw new Error("Cookies set in remote functions must have an absolute path");
}
return event.cookies.set(name, value, opts);
},
delete: (name, opts) => {
if (!allow_cookies) {
throw new Error("Cannot delete cookies in `query` or `prerender` functions");
}
if (opts.path && !opts.path.startsWith("/")) {
throw new Error("Cookies deleted in remote functions must have an absolute path");
}
return event.cookies.delete(name, opts);
}
}
},
state: {
...state,
is_in_remote_function: true
}
};
const input = await with_request_store(store, get_input);
return with_request_store(store, () => fn(input));
}
function get_cache(info, state = get_request_store().state) {
let cache = state.remote_data?.get(info);
if (cache === void 0) {
cache = {};
(state.remote_data ??= /* @__PURE__ */ new Map()).set(info, cache);
}
return cache;
}
// @__NO_SIDE_EFFECTS__
function query(validate_or_fn, maybe_fn) {
const fn = maybe_fn ?? validate_or_fn;
const validate = create_validator(validate_or_fn, maybe_fn);
const __ = { type: "query", id: "", name: "" };
const wrapper = (arg) => {
if (prerendering) {
throw new Error(
`Cannot call query '${__.name}' while prerendering, as prerendered pages need static data. Use 'prerender' from $app/server instead`
);
}
const { event, state } = get_request_store();
const get_remote_function_result = () => run_remote_function(event, state, false, () => validate(arg), fn);
const promise = get_response(__, arg, state, get_remote_function_result);
promise.catch(() => {
});
promise.set = (value) => update_refresh_value(get_refresh_context(__, "set", arg), value);
promise.refresh = () => {
const refresh_context = get_refresh_context(__, "refresh", arg);
const is_immediate_refresh = !refresh_context.cache[refresh_context.cache_key];
const value = is_immediate_refresh ? promise : get_remote_function_result();
return update_refresh_value(refresh_context, value, is_immediate_refresh);
};
promise.withOverride = () => {
throw new Error(`Cannot call '${__.name}.withOverride()' on the server`);
};
return (
/** @type {RemoteQuery<Output>} */
promise
);
};
Object.defineProperty(wrapper, "__", { value: __ });
return wrapper;
}
// @__NO_SIDE_EFFECTS__
function batch(validate_or_fn, maybe_fn) {
const fn = maybe_fn ?? validate_or_fn;
const validate = create_validator(validate_or_fn, maybe_fn);
const __ = {
type: "query_batch",
id: "",
name: "",
run: async (args, options) => {
const { event, state } = get_request_store();
return run_remote_function(
event,
state,
false,
async () => Promise.all(args.map(validate)),
async (input) => {
const get_result = await fn(input);
return Promise.all(
input.map(async (arg, i) => {
try {
return { type: "result", data: get_result(arg, i) };
} catch (error2) {
return {
type: "error",
error: await handle_error_and_jsonify(event, state, options, error2),
status: error2 instanceof HttpError || error2 instanceof SvelteKitError ? error2.status : 500
};
}
})
);
}
);
}
};
let batching = { args: [], resolvers: [] };
const wrapper = (arg) => {
if (prerendering) {
throw new Error(
`Cannot call query.batch '${__.name}' while prerendering, as prerendered pages need static data. Use 'prerender' from $app/server instead`
);
}
const { event, state } = get_request_store();
const get_remote_function_result = () => {
return new Promise((resolve, reject) => {
batching.args.push(arg);
batching.resolvers.push({ resolve, reject });
if (batching.args.length > 1) return;
setTimeout(async () => {
const batched = batching;
batching = { args: [], resolvers: [] };
try {
return await run_remote_function(
event,
state,
false,
async () => Promise.all(batched.args.map(validate)),
async (input) => {
const get_result = await fn(input);
for (let i = 0; i < batched.resolvers.length; i++) {
try {
batched.resolvers[i].resolve(get_result(input[i], i));
} catch (error2) {
batched.resolvers[i].reject(error2);
}
}
}
);
} catch (error2) {
for (const resolver of batched.resolvers) {
resolver.reject(error2);
}
}
}, 0);
});
};
const promise = get_response(__, arg, state, get_remote_function_result);
promise.catch(() => {
});
promise.set = (value) => update_refresh_value(get_refresh_context(__, "set", arg), value);
promise.refresh = () => {
const refresh_context = get_refresh_context(__, "refresh", arg);
const is_immediate_refresh = !refresh_context.cache[refresh_context.cache_key];
const value = is_immediate_refresh ? promise : get_remote_function_result();
return update_refresh_value(refresh_context, value, is_immediate_refresh);
};
promise.withOverride = () => {
throw new Error(`Cannot call '${__.name}.withOverride()' on the server`);
};
return (
/** @type {RemoteQuery<Output>} */
promise
);
};
Object.defineProperty(wrapper, "__", { value: __ });
return wrapper;
}
Object.defineProperty(query, "batch", { value: batch, enumerable: true });
function get_refresh_context(__, action, arg) {
const { state } = get_request_store();
const { refreshes } = state;
if (!refreshes) {
const name = __.type === "query_batch" ? `query.batch '${__.name}'` : `query '${__.name}'`;
throw new Error(
`Cannot call ${action} on ${name} because it is not executed in the context of a command/form remote function`
);
}
const cache = get_cache(__, state);
const cache_key = stringify_remote_arg(arg, state.transport);
const refreshes_key = create_remote_key(__.id, cache_key);
return { __, state, refreshes, refreshes_key, cache, cache_key };
}
function update_refresh_value({ __, refreshes, refreshes_key, cache, cache_key }, value, is_immediate_refresh = false) {
const promise = Promise.resolve(value);
if (!is_immediate_refresh) {
cache[cache_key] = promise;
}
if (__.id) {
refreshes[refreshes_key] = promise;
}
return promise.then(() => {
});
}
export {
get_response as a,
create_validator as c,
get_cache as g,
parse_remote_response as p,
query as q,
run_remote_function as r
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
import { b as decode_params } from "./url.js";
function affects_path(segment) {
return segment !== "" && !/^\([^)]+\)$/.test(segment);
}
function get_route_segments(route) {
return route.slice(1).split("/").filter(affects_path);
}
function exec(match, params, matchers) {
const result = {};
const values = match.slice(1);
const values_needing_match = values.filter((value) => value !== void 0);
let buffered = 0;
for (let i = 0; i < params.length; i += 1) {
const param = params[i];
let value = values[i - buffered];
if (param.chained && param.rest && buffered) {
value = values.slice(i - buffered, i + 1).filter((s) => s).join("/");
buffered = 0;
}
if (value === void 0) {
if (param.rest) {
value = "";
} else {
continue;
}
}
if (!param.matcher || matchers[param.matcher](value)) {
result[param.name] = value;
const next_param = params[i + 1];
const next_value = values[i + 1];
if (next_param && !next_param.rest && next_param.optional && next_value && param.chained) {
buffered = 0;
}
if (!next_param && !next_value && Object.keys(result).length === values_needing_match.length) {
buffered = 0;
}
continue;
}
if (param.optional && param.chained) {
buffered++;
continue;
}
return;
}
if (buffered) return;
return result;
}
const basic_param_pattern = /\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;
function resolve_route(id, params) {
const segments = get_route_segments(id);
const has_id_trailing_slash = id.endsWith("/");
return "/" + segments.map(
(segment) => segment.replace(basic_param_pattern, (_, optional, rest, name) => {
const param_value = params[name];
if (!param_value) {
if (optional) return "";
if (rest && param_value !== void 0) return "";
throw new Error(`Missing parameter '${name}' in route ${id}`);
}
if (param_value.startsWith("/") || param_value.endsWith("/"))
throw new Error(
`Parameter '${name}' in route ${id} cannot start or end with a slash -- this would cause an invalid route like foo//bar`
);
return param_value;
})
).filter(Boolean).join("/") + (has_id_trailing_slash ? "/" : "");
}
function find_route(path, routes, matchers) {
for (const route of routes) {
const match = route.pattern.exec(path);
if (!match) continue;
const matched = exec(match, route.params, matchers);
if (matched) {
return {
route,
params: decode_params(matched)
};
}
}
return null;
}
export {
find_route as f,
resolve_route as r
};

View File

@@ -0,0 +1,27 @@
let base = "";
let assets = base;
const app_dir = "_app";
const relative = true;
const initial = { base, assets };
const initial_base = initial.base;
function override(paths) {
base = paths.base;
assets = paths.assets;
}
function reset() {
base = initial.base;
assets = initial.assets;
}
function set_assets(path) {
assets = initial.assets = path;
}
export {
assets as a,
base as b,
app_dir as c,
reset as d,
initial_base as i,
override as o,
relative as r,
set_assets as s
};

View File

@@ -0,0 +1,14 @@
let private_env = {};
let public_env = {};
function set_private_env(environment) {
private_env = environment;
}
function set_public_env(environment) {
public_env = environment;
}
export {
set_public_env as a,
private_env as b,
public_env as p,
set_private_env as s
};

View File

@@ -0,0 +1,789 @@
import { json, text } from "@sveltejs/kit";
import { SvelteKitError, HttpError } from "@sveltejs/kit/internal";
import { with_request_store } from "@sveltejs/kit/internal/server";
import * as devalue from "devalue";
import { t as text_decoder, b as base64_encode, c as base64_decode } from "./utils.js";
const SVELTE_KIT_ASSETS = "/_svelte_kit_assets";
const ENDPOINT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
const MUTATIVE_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
const PAGE_METHODS = ["GET", "POST", "HEAD"];
function set_nested_value(object, path_string, value) {
if (path_string.startsWith("n:")) {
path_string = path_string.slice(2);
value = value === "" ? void 0 : parseFloat(value);
} else if (path_string.startsWith("b:")) {
path_string = path_string.slice(2);
value = value === "on";
}
deep_set(object, split_path(path_string), value);
}
function convert_formdata(data) {
const result = {};
for (let key of data.keys()) {
const is_array = key.endsWith("[]");
let values = data.getAll(key);
if (is_array) key = key.slice(0, -2);
if (values.length > 1 && !is_array) {
throw new Error(`Form cannot contain duplicated keys — "${key}" has ${values.length} values`);
}
values = values.filter(
(entry) => typeof entry === "string" || entry.name !== "" || entry.size > 0
);
if (key.startsWith("n:")) {
key = key.slice(2);
values = values.map((v) => v === "" ? void 0 : parseFloat(
/** @type {string} */
v
));
} else if (key.startsWith("b:")) {
key = key.slice(2);
values = values.map((v) => v === "on");
}
set_nested_value(result, key, is_array ? values : values[0]);
}
return result;
}
const BINARY_FORM_CONTENT_TYPE = "application/x-sveltekit-formdata";
const BINARY_FORM_VERSION = 0;
const HEADER_BYTES = 1 + 4 + 2;
async function deserialize_binary_form(request) {
if (request.headers.get("content-type") !== BINARY_FORM_CONTENT_TYPE) {
const form_data = await request.formData();
return { data: convert_formdata(form_data), meta: {}, form_data };
}
if (!request.body) {
throw deserialize_error("no body");
}
const content_length = parseInt(request.headers.get("content-length") ?? "");
if (Number.isNaN(content_length)) {
throw deserialize_error("invalid Content-Length header");
}
const reader = request.body.getReader();
const chunks = [];
function get_chunk(index) {
if (index in chunks) return chunks[index];
let i = chunks.length;
while (i <= index) {
chunks[i] = reader.read().then((chunk) => chunk.value);
i++;
}
return chunks[index];
}
async function get_buffer(offset, length) {
let start_chunk;
let chunk_start = 0;
let chunk_index;
for (chunk_index = 0; ; chunk_index++) {
const chunk = await get_chunk(chunk_index);
if (!chunk) return null;
const chunk_end = chunk_start + chunk.byteLength;
if (offset >= chunk_start && offset < chunk_end) {
start_chunk = chunk;
break;
}
chunk_start = chunk_end;
}
if (offset + length <= chunk_start + start_chunk.byteLength) {
return start_chunk.subarray(offset - chunk_start, offset + length - chunk_start);
}
const chunks2 = [start_chunk.subarray(offset - chunk_start)];
let cursor = start_chunk.byteLength - offset + chunk_start;
while (cursor < length) {
chunk_index++;
let chunk = await get_chunk(chunk_index);
if (!chunk) return null;
if (chunk.byteLength > length - cursor) {
chunk = chunk.subarray(0, length - cursor);
}
chunks2.push(chunk);
cursor += chunk.byteLength;
}
const buffer = new Uint8Array(length);
cursor = 0;
for (const chunk of chunks2) {
buffer.set(chunk, cursor);
cursor += chunk.byteLength;
}
return buffer;
}
const header = await get_buffer(0, HEADER_BYTES);
if (!header) throw deserialize_error("too short");
if (header[0] !== BINARY_FORM_VERSION) {
throw deserialize_error(`got version ${header[0]}, expected version ${BINARY_FORM_VERSION}`);
}
const header_view = new DataView(header.buffer, header.byteOffset, header.byteLength);
const data_length = header_view.getUint32(1, true);
if (HEADER_BYTES + data_length > content_length) {
throw deserialize_error("data overflow");
}
const file_offsets_length = header_view.getUint16(5, true);
if (HEADER_BYTES + data_length + file_offsets_length > content_length) {
throw deserialize_error("file offset table overflow");
}
const data_buffer = await get_buffer(HEADER_BYTES, data_length);
if (!data_buffer) throw deserialize_error("data too short");
let file_offsets;
let files_start_offset;
if (file_offsets_length > 0) {
const file_offsets_buffer = await get_buffer(HEADER_BYTES + data_length, file_offsets_length);
if (!file_offsets_buffer) throw deserialize_error("file offset table too short");
const parsed_offsets = JSON.parse(text_decoder.decode(file_offsets_buffer));
if (!Array.isArray(parsed_offsets) || parsed_offsets.some((n) => typeof n !== "number" || !Number.isInteger(n) || n < 0)) {
throw deserialize_error("invalid file offset table");
}
file_offsets = /** @type {Array<number>} */
parsed_offsets;
files_start_offset = HEADER_BYTES + data_length + file_offsets_length;
}
const file_spans = [];
const [data, meta] = devalue.parse(text_decoder.decode(data_buffer), {
File: ([name, type, size, last_modified, index]) => {
if (typeof name !== "string" || typeof type !== "string" || typeof size !== "number" || typeof last_modified !== "number" || typeof index !== "number") {
throw deserialize_error("invalid file metadata");
}
let offset = file_offsets[index];
if (offset === void 0) {
throw deserialize_error("duplicate file offset table index");
}
file_offsets[index] = void 0;
offset += files_start_offset;
if (offset + size > content_length) {
throw deserialize_error("file data overflow");
}
file_spans.push({ offset, size });
return new Proxy(new LazyFile(name, type, size, last_modified, get_chunk, offset), {
getPrototypeOf() {
return File.prototype;
}
});
}
});
file_spans.sort((a, b) => a.offset - b.offset || a.size - b.size);
for (let i = 1; i < file_spans.length; i++) {
const previous = file_spans[i - 1];
const current = file_spans[i];
const previous_end = previous.offset + previous.size;
if (previous_end < current.offset) {
throw deserialize_error("gaps in file data");
}
if (previous_end > current.offset) {
throw deserialize_error("overlapping file data");
}
}
void (async () => {
let has_more = true;
while (has_more) {
const chunk = await get_chunk(chunks.length);
has_more = !!chunk;
}
})();
return { data, meta, form_data: null };
}
function deserialize_error(message) {
return new SvelteKitError(400, "Bad Request", `Could not deserialize binary form: ${message}`);
}
class LazyFile {
/** @type {(index: number) => Promise<Uint8Array<ArrayBuffer> | undefined>} */
#get_chunk;
/** @type {number} */
#offset;
/**
* @param {string} name
* @param {string} type
* @param {number} size
* @param {number} last_modified
* @param {(index: number) => Promise<Uint8Array<ArrayBuffer> | undefined>} get_chunk
* @param {number} offset
*/
constructor(name, type, size, last_modified, get_chunk, offset) {
this.name = name;
this.type = type;
this.size = size;
this.lastModified = last_modified;
this.webkitRelativePath = "";
this.#get_chunk = get_chunk;
this.#offset = offset;
this.arrayBuffer = this.arrayBuffer.bind(this);
this.bytes = this.bytes.bind(this);
this.slice = this.slice.bind(this);
this.stream = this.stream.bind(this);
this.text = this.text.bind(this);
}
/** @type {ArrayBuffer | undefined} */
#buffer;
async arrayBuffer() {
this.#buffer ??= await new Response(this.stream()).arrayBuffer();
return this.#buffer;
}
async bytes() {
return new Uint8Array(await this.arrayBuffer());
}
/**
* @param {number=} start
* @param {number=} end
* @param {string=} contentType
*/
slice(start = 0, end = this.size, contentType = this.type) {
if (start < 0) {
start = Math.max(this.size + start, 0);
} else {
start = Math.min(start, this.size);
}
if (end < 0) {
end = Math.max(this.size + end, 0);
} else {
end = Math.min(end, this.size);
}
const size = Math.max(end - start, 0);
const file = new LazyFile(
this.name,
contentType,
size,
this.lastModified,
this.#get_chunk,
this.#offset + start
);
return file;
}
stream() {
let cursor = 0;
let chunk_index = 0;
return new ReadableStream({
start: async (controller) => {
let chunk_start = 0;
let start_chunk;
for (chunk_index = 0; ; chunk_index++) {
const chunk = await this.#get_chunk(chunk_index);
if (!chunk) return null;
const chunk_end = chunk_start + chunk.byteLength;
if (this.#offset >= chunk_start && this.#offset < chunk_end) {
start_chunk = chunk;
break;
}
chunk_start = chunk_end;
}
if (this.#offset + this.size <= chunk_start + start_chunk.byteLength) {
controller.enqueue(
start_chunk.subarray(this.#offset - chunk_start, this.#offset + this.size - chunk_start)
);
controller.close();
} else {
controller.enqueue(start_chunk.subarray(this.#offset - chunk_start));
cursor = start_chunk.byteLength - this.#offset + chunk_start;
}
},
pull: async (controller) => {
chunk_index++;
let chunk = await this.#get_chunk(chunk_index);
if (!chunk) {
controller.error("incomplete file data");
controller.close();
return;
}
if (chunk.byteLength > this.size - cursor) {
chunk = chunk.subarray(0, this.size - cursor);
}
controller.enqueue(chunk);
cursor += chunk.byteLength;
if (cursor >= this.size) {
controller.close();
}
}
});
}
async text() {
return text_decoder.decode(await this.arrayBuffer());
}
}
const path_regex = /^[a-zA-Z_$]\w*(\.[a-zA-Z_$]\w*|\[\d+\])*$/;
function split_path(path) {
if (!path_regex.test(path)) {
throw new Error(`Invalid path ${path}`);
}
return path.split(/\.|\[|\]/).filter(Boolean);
}
function check_prototype_pollution(key) {
if (key === "__proto__" || key === "constructor" || key === "prototype") {
throw new Error(
`Invalid key "${key}"`
);
}
}
function deep_set(object, keys, value) {
let current = object;
for (let i = 0; i < keys.length - 1; i += 1) {
const key = keys[i];
check_prototype_pollution(key);
const is_array = /^\d+$/.test(keys[i + 1]);
const exists = Object.hasOwn(current, key);
const inner = current[key];
if (exists && is_array !== Array.isArray(inner)) {
throw new Error(`Invalid array key ${keys[i + 1]}`);
}
if (!exists) {
current[key] = is_array ? [] : {};
}
current = current[key];
}
const final_key = keys[keys.length - 1];
check_prototype_pollution(final_key);
current[final_key] = value;
}
function normalize_issue(issue, server = false) {
const normalized = { name: "", path: [], message: issue.message, server };
if (issue.path !== void 0) {
let name = "";
for (const segment of issue.path) {
const key = (
/** @type {string | number} */
typeof segment === "object" ? segment.key : segment
);
normalized.path.push(key);
if (typeof key === "number") {
name += `[${key}]`;
} else if (typeof key === "string") {
name += name === "" ? key : "." + key;
}
}
normalized.name = name;
}
return normalized;
}
function flatten_issues(issues) {
const result = {};
for (const issue of issues) {
(result.$ ??= []).push(issue);
let name = "";
if (issue.path !== void 0) {
for (const key of issue.path) {
if (typeof key === "number") {
name += `[${key}]`;
} else if (typeof key === "string") {
name += name === "" ? key : "." + key;
}
(result[name] ??= []).push(issue);
}
}
}
return result;
}
function deep_get(object, path) {
let current = object;
for (const key of path) {
if (current == null || typeof current !== "object") {
return current;
}
current = current[key];
}
return current;
}
function create_field_proxy(target, get_input, set_input, get_issues, path = []) {
const get_value = () => {
return deep_get(get_input(), path);
};
return new Proxy(target, {
get(target2, prop) {
if (typeof prop === "symbol") return target2[prop];
if (/^\d+$/.test(prop)) {
return create_field_proxy({}, get_input, set_input, get_issues, [
...path,
parseInt(prop, 10)
]);
}
const key = build_path_string(path);
if (prop === "set") {
const set_func = function(newValue) {
set_input(path, newValue);
return newValue;
};
return create_field_proxy(set_func, get_input, set_input, get_issues, [...path, prop]);
}
if (prop === "value") {
return create_field_proxy(get_value, get_input, set_input, get_issues, [...path, prop]);
}
if (prop === "issues" || prop === "allIssues") {
const issues_func = () => {
const all_issues = get_issues()[key === "" ? "$" : key];
if (prop === "allIssues") {
return all_issues?.map((issue) => ({
path: issue.path,
message: issue.message
}));
}
return all_issues?.filter((issue) => issue.name === key)?.map((issue) => ({
path: issue.path,
message: issue.message
}));
};
return create_field_proxy(issues_func, get_input, set_input, get_issues, [...path, prop]);
}
if (prop === "as") {
const as_func = (type, input_value) => {
const is_array = type === "file multiple" || type === "select multiple" || type === "checkbox" && typeof input_value === "string";
const prefix = type === "number" || type === "range" ? "n:" : type === "checkbox" && !is_array ? "b:" : "";
const base_props = {
name: prefix + key + (is_array ? "[]" : ""),
get "aria-invalid"() {
const issues = get_issues();
return key in issues ? "true" : void 0;
}
};
if (type !== "text" && type !== "select" && type !== "select multiple") {
base_props.type = type === "file multiple" ? "file" : type;
}
if (type === "submit" || type === "hidden") {
return Object.defineProperties(base_props, {
value: { value: input_value, enumerable: true }
});
}
if (type === "select" || type === "select multiple") {
return Object.defineProperties(base_props, {
multiple: { value: is_array, enumerable: true },
value: {
enumerable: true,
get() {
return get_value();
}
}
});
}
if (type === "checkbox" || type === "radio") {
return Object.defineProperties(base_props, {
value: { value: input_value ?? "on", enumerable: true },
checked: {
enumerable: true,
get() {
const value = get_value();
if (type === "radio") {
return value === input_value;
}
if (is_array) {
return (value ?? []).includes(input_value);
}
return value;
}
}
});
}
if (type === "file" || type === "file multiple") {
return Object.defineProperties(base_props, {
multiple: { value: is_array, enumerable: true },
files: {
enumerable: true,
get() {
const value = get_value();
if (value instanceof File) {
if (typeof DataTransfer !== "undefined") {
const fileList = new DataTransfer();
fileList.items.add(value);
return fileList.files;
}
return { 0: value, length: 1 };
}
if (Array.isArray(value) && value.every((f) => f instanceof File)) {
if (typeof DataTransfer !== "undefined") {
const fileList = new DataTransfer();
value.forEach((file) => fileList.items.add(file));
return fileList.files;
}
const fileListLike = { length: value.length };
value.forEach((file, index) => {
fileListLike[index] = file;
});
return fileListLike;
}
return null;
}
}
});
}
return Object.defineProperties(base_props, {
value: {
enumerable: true,
get() {
const value = get_value();
return value != null ? String(value) : "";
}
}
});
};
return create_field_proxy(as_func, get_input, set_input, get_issues, [...path, "as"]);
}
return create_field_proxy({}, get_input, set_input, get_issues, [...path, prop]);
}
});
}
function build_path_string(path) {
let result = "";
for (const segment of path) {
if (typeof segment === "number") {
result += `[${segment}]`;
} else {
result += result === "" ? segment : "." + segment;
}
}
return result;
}
function negotiate(accept, types) {
const parts = [];
accept.split(",").forEach((str, i) => {
const match = /([^/ \t]+)\/([^; \t]+)[ \t]*(?:;[ \t]*q=([0-9.]+))?/.exec(str);
if (match) {
const [, type, subtype, q = "1"] = match;
parts.push({ type, subtype, q: +q, i });
}
});
parts.sort((a, b) => {
if (a.q !== b.q) {
return b.q - a.q;
}
if (a.subtype === "*" !== (b.subtype === "*")) {
return a.subtype === "*" ? 1 : -1;
}
if (a.type === "*" !== (b.type === "*")) {
return a.type === "*" ? 1 : -1;
}
return a.i - b.i;
});
let accepted;
let min_priority = Infinity;
for (const mimetype of types) {
const [type, subtype] = mimetype.split("/");
const priority = parts.findIndex(
(part) => (part.type === type || part.type === "*") && (part.subtype === subtype || part.subtype === "*")
);
if (priority !== -1 && priority < min_priority) {
accepted = mimetype;
min_priority = priority;
}
}
return accepted;
}
function is_content_type(request, ...types) {
const type = request.headers.get("content-type")?.split(";", 1)[0].trim() ?? "";
return types.includes(type.toLowerCase());
}
function is_form_content_type(request) {
return is_content_type(
request,
"application/x-www-form-urlencoded",
"multipart/form-data",
"text/plain",
BINARY_FORM_CONTENT_TYPE
);
}
function coalesce_to_error(err) {
return err instanceof Error || err && /** @type {any} */
err.name && /** @type {any} */
err.message ? (
/** @type {Error} */
err
) : new Error(JSON.stringify(err));
}
function normalize_error(error) {
return (
/** @type {import('../exports/internal/index.js').Redirect | HttpError | SvelteKitError | Error} */
error
);
}
function get_status(error) {
return error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500;
}
function get_message(error) {
return error instanceof SvelteKitError ? error.text : "Internal Error";
}
const escape_html_attr_dict = {
"&": "&amp;",
'"': "&quot;"
// Svelte also escapes < because the escape function could be called inside a `noscript` there
// https://github.com/sveltejs/svelte/security/advisories/GHSA-8266-84wp-wv5c
// However, that doesn't apply in SvelteKit
};
const escape_html_dict = {
"&": "&amp;",
"<": "&lt;"
};
const surrogates = (
// high surrogate without paired low surrogate
"[\\ud800-\\udbff](?![\\udc00-\\udfff])|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\udc00-\\udfff]"
);
const escape_html_attr_regex = new RegExp(
`[${Object.keys(escape_html_attr_dict).join("")}]|` + surrogates,
"g"
);
const escape_html_regex = new RegExp(
`[${Object.keys(escape_html_dict).join("")}]|` + surrogates,
"g"
);
function escape_html(str, is_attr) {
const dict = is_attr ? escape_html_attr_dict : escape_html_dict;
const escaped_str = str.replace(is_attr ? escape_html_attr_regex : escape_html_regex, (match) => {
if (match.length === 2) {
return match;
}
return dict[match] ?? `&#${match.charCodeAt(0)};`;
});
return escaped_str;
}
function method_not_allowed(mod, method) {
return text(`${method} method not allowed`, {
status: 405,
headers: {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
// "The server must generate an Allow header field in a 405 status code response"
allow: allowed_methods(mod).join(", ")
}
});
}
function allowed_methods(mod) {
const allowed = ENDPOINT_METHODS.filter((method) => method in mod);
if ("GET" in mod && !("HEAD" in mod)) {
allowed.push("HEAD");
}
return allowed;
}
function get_global_name(options) {
return `__sveltekit_${options.version_hash}`;
}
function static_error_page(options, status, message) {
let page = options.templates.error({ status, message: escape_html(message) });
return text(page, {
headers: { "content-type": "text/html; charset=utf-8" },
status
});
}
async function handle_fatal_error(event, state, options, error) {
error = error instanceof HttpError ? error : coalesce_to_error(error);
const status = get_status(error);
const body = await handle_error_and_jsonify(event, state, options, error);
const type = negotiate(event.request.headers.get("accept") || "text/html", [
"application/json",
"text/html"
]);
if (event.isDataRequest || type === "application/json") {
return json(body, {
status
});
}
return static_error_page(options, status, body.message);
}
async function handle_error_and_jsonify(event, state, options, error) {
if (error instanceof HttpError) {
return { message: "Unknown Error", ...error.body };
}
const status = get_status(error);
const message = get_message(error);
return await with_request_store(
{ event, state },
() => options.hooks.handleError({ error, event, status, message })
) ?? { message };
}
function redirect_response(status, location) {
const response = new Response(void 0, {
status,
headers: { location }
});
return response;
}
function clarify_devalue_error(event, error) {
if (error.path) {
return `Data returned from \`load\` while rendering ${event.route.id} is not serializable: ${error.message} (${error.path}). If you need to serialize/deserialize custom types, use transport hooks: https://svelte.dev/docs/kit/hooks#Universal-hooks-transport.`;
}
if (error.path === "") {
return `Data returned from \`load\` while rendering ${event.route.id} is not a plain object`;
}
return error.message;
}
function serialize_uses(node) {
const uses = {};
if (node.uses && node.uses.dependencies.size > 0) {
uses.dependencies = Array.from(node.uses.dependencies);
}
if (node.uses && node.uses.search_params.size > 0) {
uses.search_params = Array.from(node.uses.search_params);
}
if (node.uses && node.uses.params.size > 0) {
uses.params = Array.from(node.uses.params);
}
if (node.uses?.parent) uses.parent = 1;
if (node.uses?.route) uses.route = 1;
if (node.uses?.url) uses.url = 1;
return uses;
}
function has_prerendered_path(manifest, pathname) {
return manifest._.prerendered_routes.has(pathname) || pathname.at(-1) === "/" && manifest._.prerendered_routes.has(pathname.slice(0, -1));
}
function format_server_error(status, error, event) {
const formatted_text = `
\x1B[1;31m[${status}] ${event.request.method} ${event.url.pathname}\x1B[0m`;
if (status === 404) {
return formatted_text;
}
return `${formatted_text}
${error.stack}`;
}
function get_node_type(node_id) {
const parts = node_id?.split("/");
const filename = parts?.at(-1);
if (!filename) return "unknown";
const dot_parts = filename.split(".");
return dot_parts.slice(0, -1).join(".");
}
const INVALIDATED_PARAM = "x-sveltekit-invalidated";
const TRAILING_SLASH_PARAM = "x-sveltekit-trailing-slash";
function stringify(data, transport) {
const encoders = Object.fromEntries(Object.entries(transport).map(([k, v]) => [k, v.encode]));
return devalue.stringify(data, encoders);
}
function stringify_remote_arg(value, transport) {
if (value === void 0) return "";
const json_string = stringify(value, transport);
const bytes = new TextEncoder().encode(json_string);
return base64_encode(bytes).replaceAll("=", "").replaceAll("+", "-").replaceAll("/", "_");
}
function parse_remote_arg(string, transport) {
if (!string) return void 0;
const json_string = text_decoder.decode(
// no need to add back `=` characters, atob can handle it
base64_decode(string.replaceAll("-", "+").replaceAll("_", "/"))
);
const decoders = Object.fromEntries(Object.entries(transport).map(([k, v]) => [k, v.decode]));
return devalue.parse(json_string, decoders);
}
function create_remote_key(id, payload) {
return id + "/" + payload;
}
export {
ENDPOINT_METHODS as E,
INVALIDATED_PARAM as I,
MUTATIVE_METHODS as M,
PAGE_METHODS as P,
SVELTE_KIT_ASSETS as S,
TRAILING_SLASH_PARAM as T,
normalize_error as a,
get_global_name as b,
clarify_devalue_error as c,
get_node_type as d,
escape_html as e,
create_remote_key as f,
get_status as g,
handle_error_and_jsonify as h,
is_form_content_type as i,
static_error_page as j,
stringify as k,
deserialize_binary_form as l,
method_not_allowed as m,
negotiate as n,
has_prerendered_path as o,
parse_remote_arg as p,
handle_fatal_error as q,
redirect_response as r,
serialize_uses as s,
format_server_error as t,
create_field_proxy as u,
normalize_issue as v,
set_nested_value as w,
flatten_issues as x,
deep_set as y,
stringify_remote_arg as z
};

View File

@@ -0,0 +1,18 @@
import "clsx";
import { n as noop } from "./index3.js";
import "./url.js";
import "@sveltejs/kit/internal/server";
import "./root.js";
const is_legacy = noop.toString().includes("$$") || /function \w+\(\) \{\}/.test(noop.toString());
if (is_legacy) {
({
data: {},
form: null,
error: null,
params: {},
route: { id: null },
state: {},
status: -1,
url: new URL("https://example.com")
});
}

View File

@@ -0,0 +1,106 @@
const SCHEME = /^[a-z][a-z\d+\-.]+:/i;
const internal = new URL("sveltekit-internal://");
function resolve(base, path) {
if (path[0] === "/" && path[1] === "/") return path;
let url = new URL(base, internal);
url = new URL(path, url);
return url.protocol === internal.protocol ? url.pathname + url.search + url.hash : url.href;
}
function normalize_path(path, trailing_slash) {
if (path === "/" || trailing_slash === "ignore") return path;
if (trailing_slash === "never") {
return path.endsWith("/") ? path.slice(0, -1) : path;
} else if (trailing_slash === "always" && !path.endsWith("/")) {
return path + "/";
}
return path;
}
function decode_pathname(pathname) {
return pathname.split("%25").map(decodeURI).join("%25");
}
function decode_params(params) {
for (const key in params) {
params[key] = decodeURIComponent(params[key]);
}
return params;
}
function make_trackable(url, callback, search_params_callback, allow_hash = false) {
const tracked = new URL(url);
Object.defineProperty(tracked, "searchParams", {
value: new Proxy(tracked.searchParams, {
get(obj, key) {
if (key === "get" || key === "getAll" || key === "has") {
return (param, ...rest) => {
search_params_callback(param);
return obj[key](param, ...rest);
};
}
callback();
const value = Reflect.get(obj, key);
return typeof value === "function" ? value.bind(obj) : value;
}
}),
enumerable: true,
configurable: true
});
const tracked_url_properties = ["href", "pathname", "search", "toString", "toJSON"];
if (allow_hash) tracked_url_properties.push("hash");
for (const property of tracked_url_properties) {
Object.defineProperty(tracked, property, {
get() {
callback();
return url[property];
},
enumerable: true,
configurable: true
});
}
{
tracked[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")] = (depth, opts, inspect) => {
return inspect(url, opts);
};
tracked.searchParams[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")] = (depth, opts, inspect) => {
return inspect(url.searchParams, opts);
};
}
if (!allow_hash) {
disable_hash(tracked);
}
return tracked;
}
function disable_hash(url) {
allow_nodejs_console_log(url);
Object.defineProperty(url, "hash", {
get() {
throw new Error(
"Cannot access event.url.hash. Consider using `page.url.hash` inside a component instead"
);
}
});
}
function disable_search(url) {
allow_nodejs_console_log(url);
for (const property of ["search", "searchParams"]) {
Object.defineProperty(url, property, {
get() {
throw new Error(`Cannot access url.${property} on a page with prerendering enabled`);
}
});
}
}
function allow_nodejs_console_log(url) {
{
url[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")] = (depth, opts, inspect) => {
return inspect(new URL(url), opts);
};
}
}
export {
SCHEME as S,
decode_pathname as a,
decode_params as b,
disable_search as d,
make_trackable as m,
normalize_path as n,
resolve as r
};

View File

@@ -0,0 +1,43 @@
const text_encoder = new TextEncoder();
const text_decoder = new TextDecoder();
function get_relative_path(from, to) {
const from_parts = from.split(/[/\\]/);
const to_parts = to.split(/[/\\]/);
from_parts.pop();
while (from_parts[0] === to_parts[0]) {
from_parts.shift();
to_parts.shift();
}
let i = from_parts.length;
while (i--) from_parts[i] = "..";
return from_parts.concat(to_parts).join("/");
}
function base64_encode(bytes) {
if (globalThis.Buffer) {
return globalThis.Buffer.from(bytes).toString("base64");
}
let binary = "";
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
function base64_decode(encoded) {
if (globalThis.Buffer) {
const buffer = globalThis.Buffer.from(encoded, "base64");
return new Uint8Array(buffer);
}
const binary = atob(encoded);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
export {
text_encoder as a,
base64_encode as b,
base64_decode as c,
get_relative_path as g,
text_decoder as t
};