Working run architecture with redis

This commit is contained in:
2026-02-08 14:11:11 +01:00
parent 80336104bc
commit 74ba95f6c9
4 changed files with 457 additions and 34 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ export async function PUT(req: NextRequest) {
const runId = randomUUID();
await Core.addRun({
await Core.addLiveRun({
problem,
category,
id: runId,
+63 -33
View File
@@ -3,57 +3,85 @@
import "server-only";
import { LiveRun, LiveRunMove } from "./types";
import { SupabaseClient } from "@supabase/supabase-js";
import Redis from "ioredis";
const liveRuns: LiveRun[] = [];
const liveRunMoves: Record<string, LiveRunMove[]> = {};
const redis = new Redis(process.env.REDIS_URL!);
/** -------------------- Live Runs in Redis -------------------- */
/**
* Add a live run to Redis
*/
export async function addLiveRun(run: LiveRun) {
const exists = await redis.exists(`liveRun:${run.id}`);
if (exists) throw new Error("Duplicate run");
await redis.set(`liveRun:${run.id}`, JSON.stringify(run));
}
/**
* Get a live run from Redis
*/
export async function getLiveRun(id: string): Promise<LiveRun | null> {
const data = await redis.get(`liveRun:${id}`);
if (!data) return null;
return JSON.parse(data) as LiveRun;
}
/**
* Get all live runs
*/
export async function getLiveRuns(): Promise<LiveRun[]> {
return liveRuns;
const keys = await redis.keys("liveRun:*");
if (keys.length === 0) return [];
const runs = await redis.mget(...keys);
return runs.filter(Boolean).map((r) => JSON.parse(r!) as LiveRun);
}
export async function getLiveRun(id: string): Promise<LiveRun | undefined> {
return liveRuns.find((r) => r.id === id);
/**
* Remove a live run from Redis
*/
export async function removeLiveRun(id: string) {
await redis.del(`liveRun:${id}`, `liveRunMoves:${id}`);
}
export async function getLiveRunMoves(
id: string,
): Promise<LiveRunMove[] | undefined> {
return liveRunMoves[id];
}
export async function addRun(run: LiveRun) {
if (liveRuns.find((r) => r.id === run.id || r.username === run.username))
throw "Duplicate run";
liveRuns.push(run);
}
/** -------------------- Live Run Moves -------------------- */
/**
* Add moves to a live run (ephemeral)
*/
export async function addLiveRunMoves(id: string, moves: LiveRunMove[]) {
if (!liveRunMoves[id]) throw "Invalid run";
const runExists = await redis.exists(`liveRun:${id}`);
if (!runExists) throw new Error("Invalid run");
// Add the moves immediately
liveRunMoves[id] = [...liveRunMoves[id], ...moves];
const pipeline = redis.pipeline();
moves.forEach((move) => {
pipeline.rpush(`liveRunMoves:${id}`, JSON.stringify(move));
});
// After 3 seconds, remove only the moves we just added
setTimeout(() => {
liveRunMoves[id] = liveRunMoves[id].filter((move) => !moves.includes(move));
}, 3000);
// Optional: keep moves ephemeral automatically after 3s
pipeline.expire(`liveRunMoves:${id}`, 3);
await pipeline.exec();
}
export async function removeLiveRun(runId: string) {
delete liveRunMoves[runId];
const index = liveRuns.findIndex((run) => run.id === runId);
if (index !== -1) {
liveRuns.splice(index, 1);
}
/**
* Get live moves for a run
*/
export async function getLiveRunMoves(id: string): Promise<LiveRunMove[]> {
const moves = await redis.lrange(`liveRunMoves:${id}`, 0, -1);
return moves.map((m) => JSON.parse(m)) as LiveRunMove[];
}
/** -------------------- Submit Run -------------------- */
/**
* Submit a run to Supabase (persistent storage)
* and remove it from Redis
*/
export async function submitRun(
supabase: SupabaseClient,
run: LiveRun,
user_id: string,
user_id: string
) {
const now = new Date();
const durationMs = now.getTime() - run.start;
@@ -81,5 +109,7 @@ export async function submitRun(
throw error;
}
await removeLiveRun(run.id);
return data;
}