Skip to content

BioKEA/games-leaderboard-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@biokea/leaderboard

Shared leaderboard client for BioKEA games. One Supabase project, one scores table, one TypeScript client.

Status: private. Used internally by every game-* repo under the BioKEA org.

Why this exists

Nine games were each rolling their own Supabase wrapper, their own daily_scores_leaderboard migration, their own anonymous-handle storage. This package centralizes all of that:

  • One Supabase project instead of nine
  • One scores table with game_id so adding a 10th game is "pick a slug"
  • One client APIsubmitScore, getTopScores, getDailyLeaderboard
  • Built-in rate limit (10 submissions/min/client) so trust-the-client doesn't mean trust-the-firehose
  • Cross-game leaderboards become a SQL query, not a federation problem

One-time setup (Supabase project)

  1. Create a new Supabase project named biokea-leaderboards
  2. Apply the migration:
    supabase db push
    # or paste migrations/0001_init_shared_leaderboard.sql into the SQL editor
  3. Copy the project URL and the publishable (anon) key into the games' .env files (see below)

That's the entire backend. No edge functions, no auth, no cron.

Game integration

In each game's .env:

VITE_SUPABASE_URL=https://<project-ref>.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=<anon-key>

In each game's package.json:

{
  "dependencies": {
    "@biokea/leaderboard": "github:BioKEA/biokea-leaderboard-js#main",
    "@supabase/supabase-js": "^2.99.2"
  }
}

In code:

import { BiokeaLeaderboard } from '@biokea/leaderboard';

const lb = new BiokeaLeaderboard({
  supabaseUrl: import.meta.env.VITE_SUPABASE_URL,
  supabaseKey: import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY,
});

// Submit a daily score
await lb.submitScore({
  gameId: 'plasmid-plinko',
  mode: 'daily',
  seed: '2026-05-02',
  score: 14820,
  playerHandle: 'sean',
  metadata: { character: 'biologist', perfectAntes: 6 },
});

// Read today's top 10
const top = await lb.getDailyLeaderboard('plasmid-plinko');

If env vars are missing the client silently no-ops — same behavior the games had before, so dev environments without Supabase configured don't crash.

API

class BiokeaLeaderboard {
  constructor(config: { supabaseUrl: string; supabaseKey: string });

  get configured: boolean;

  submitScore(submission: ScoreSubmission): Promise<SubmitResult>;

  getTopScores(opts: {
    gameId: string;
    mode: string;
    seed?: string;
    limit?: number;
  }): Promise<LeaderboardEntry[]>;

  getDailyLeaderboard(
    gameId: string,
    dateKey?: string,   // YYYY-MM-DD, defaults to today UTC
    limit?: number,
  ): Promise<LeaderboardEntry[]>;

  getPlayerRank(opts: {
    gameId: string;
    mode: string;
    seed?: string;
    score: number;
  }): Promise<number | null>;       // 1-indexed; null on error
}

SubmitResult is a discriminated union: { ok: true, id } or { ok: false, reason } where reason is 'rate_limited' | 'invalid' | 'network' | 'unconfigured'. Games can use this to show an appropriate UI (e.g. quietly drop on unconfigured, toast on rate_limited).

Schema (one table)

scores (
  id, game_id, mode, seed, score (bigint),
  player_handle, client_id (uuid in localStorage),
  metadata (jsonb), created_at
)

game_id and mode are short slugs (validated by check constraints). seed is a date string for daily mode, a hash for seeded runs, null otherwise. metadata is the escape hatch for per-game extras (character class, modifiers, kb_called, waves cleared, …).

See migrations/0001_init_shared_leaderboard.sql for the full DDL.

Game IDs (canonical list)

game_id Repo
plasmid-plinko game-plasmid-plinko
pipette-rush game-pipette-rush
evil-henchman-lair game-evil-henchman-lair
codon2048 game-codon2048
particle-survival-shooter game-particle-survival-shooter
cal-field-lab-collectible game-cal-field-lab-collectible
3d-biodiversity-collect-em-all game-3d-biodiversity-collect-em-all
pore-sequencing-basecalling game-pore-sequencing-basecalling
collider-tower-defense game-collider-tower-defense

Migrating existing games

See docs/migration-plan.md. Per-game it's:

  1. Drop supabase/migrations/*_create_daily_scores_leaderboard.sql
  2. Replace src/lib/supabase.ts with an import of @biokea/leaderboard
  3. Update score-submission and leaderboard-display call sites to the new API
  4. Delete the now-unused @supabase/supabase-js direct usage

License

MIT — see LICENSE.


Made by BioKEA.

About

Shared leaderboard client for BioKEA games.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors