Leaderboard reference

Board config

What each field decides is on boards and windows; what the rank field costs is on what it costs.

FieldDefault
key—A URL path segment, and the entry key
stored1d1 only, today
direction—desc (highest wins) or asc (lap times). Immutable
aggregation—best · latest · sum. Immutable
window—CRON, UTC. Omit for all-time. Immutable
retainkeep allClosed windows to keep
retainDayskeep allDelete older than N days. Set one, not both
min, max—Server-side bounds
trackActivityfalseThe cost lever. True writes on every submission
tiers—Classified on read
serverAuthoritativetrue

store, direction, aggregation and window are immutable once a board has an entry. Changing one raises leaderboard/board_immutable on the next submission. A new definition means a new key.

Top-level config

KeyDefault
rank"live"Or { materialize: "<cron>" }
adminScopeThe hide and remove routes
submitScopeRequired when server-authoritative
basePath/leaderboard

Routes

RouteVerified by
GET /leaderboarduser
POST /leaderboard/:boarduser + submit scope when server-authoritative
GET /leaderboard/:board/topuser
GET /leaderboard/:board/meuser
GET /leaderboard/:board/arounduser
POST /leaderboard/:board/segmentuser
PUT /leaderboard/:board/me/visibilityuser
PUT /leaderboard/:board/entries/:userId/hiddenuser + admin scope
DELETE /leaderboard/:board/entries/:userIduser + admin scope

Any read takes ?window=<key> to read a closed window.

There is no public surface. Without auth composed, every route denies. Using Leaderboard has the calls.

Limits

Segment members per query80 — D1’s 100-parameter cap, less 10 the my-rank path spends
Rank refresh chunkPaces the walk, not a row count for one statement
Stale lock reclaim1 hour, via LEADERBOARD_LOCK_STALE_MS
Storage~3 GB at 10M players, against a 10 GB cap

Tie-breaking

Score, then earliest achievedAt, then user id.

Total ordering — no two entries tie, so dense-versus-competition ranking never arises and neither is implemented.

Resubmitting the same score does not reset achievedAt.

Error codes

Each is domain/reason, like every error code in the kit.

Code
leaderboard/board_not_found
leaderboard/entry_not_found
leaderboard/board_immutableOne of the four fixed fields changed
leaderboard/invalid_scheduleThe CRON did not parse
leaderboard/score_rejectedOutside min/max
leaderboard/submit_forbiddenServer-authoritative, and no submit scope

The worker

A cron-triggered Workflow doing the retention sweep and, if materialized, the rank refresh.

Needed when rank is materialized, or any board configures retention. A live board set keeping everything needs no worker.

It need not be separate — a Workflow class, a scheduled handler and one cron trigger fold into your app Worker. A cron trigger is the only hard requirement.

A D1 advisory lock keeps one refresh running at a time; a second instance skips rather than interleaving.

What is not implemented

Dense ranking and competition ranking, because the ordering is total and neither can arise.

Any store but D1, today.

A board admin surface — boards are config.

ESC