Boards and windows

Boards are config, not rows

The board set lives in pithy.config.ts — every field of it — reviewed and deployed like the rest of your app.

There is no board admin screen and no board table to seed. Submitting and reading is the whole runtime surface, and what a board set costs to run is a calculation worth doing first — the rank field below is the expensive one. Neither Apple nor Google lets a window be a calendar month at all.

leaderboard({
  boards: [
    {
      key: "weekly-distance",
      store: "d1",
      direction: "desc",        // `asc` for lap times
      aggregation: "best",      // best | latest | sum
      window: "0 0 * * 1",      // CRON, UTC. Omit for all-time
      retain: 12,
      min: 0,
      max: 100_000,
      trackActivity: false,
    },
  ],
  rank: "live",
})

The database records only what config cannot: the entries, and a fingerprint of each board’s store, direction, aggregation and window.

Those four are immutable once a board has taken an entry

Each one is the lens stored scores are read through. Changing one reinterprets data rather than reconfiguring behavior.

Flip direction and last place becomes first.

So the next submission fails with leaderboard/board_immutable instead of silently corrupting the board.

Windows are CRON, so the calendar works

"0 0 * * *"    daily
"0 0 * * 1"    weekly, Mondays
"0 0 1 * *"    calendar month
"0 0 1 1 *"    calendar year
undefined      all-time

Every window is UTC-anchored, and each carries its own aggregation state rather than being a filter over an append log.

A score’s window key is the instant its board’s CRON last fired at or before it.

Add ?window=<key> to any read to read a closed window. That history is yours.

Why CRON rather than a duration

Because a calendar month is not a number of days.

Apple’s leaderboards take a fixed duration in minutes, hours and days — so a 30-day rolling board is expressible and a calendar month is not, because months are 28, 29, 30 or 31 days. A calendar year is unambiguously impossible. Google Play Games ships daily, weekly and all-time, with no monthly at all.

The marginal cost here was about zero: the materialized-rank option already needed a CRON parser.

Ties break by who got there first

Equal scores rank by earliest achievedAt, then by user id.

Two rounds of research produced zero verified evidence on how any vendor breaks a tie, so this is a product decision rather than a precedent — and it has a payoff.

The ordering is total. No two entries can tie, so dense-versus-competition ranking never arises and neither is implemented.

Resubmitting the same score does not reset your achievedAt. You earned first-to-reach; a replay does not cost it.

Retention keeps everything by default

Storage is never the cost driver — 3 GB at ten million players, against a 10 GB cap — so nothing is deleted unless you ask.

When you do want a limit, set one, not both:

{ key: "weekly", window: "0 0 * * 1", retain: 12 }      // product: browse the last 12 weeks
{ key: "daily",  window: "0 0 * * *", retainDays: 90 }  // compliance: delete data older than 90 days

Pick the one that matches your intent. They answer different questions, and a board with both configured has two policies that will eventually disagree.

ESC