Skip to content

API Client & Curator Auth

dicechess-analytics-ui communicates with the analytical backend database via a lightweight, typed API Client wrapper and manages position favorite annotations via Curator tokens.


The core API request wrapper is defined in src/lib/api/client.ts.

  • Response Handling: Automatically resolves standard responses. If the response content is empty, returns null. Parses JSON responses where possible, returning raw text as a fallback.
  • Error Handling: Converts non-ok HTTP statuses into a custom ApiError class containing status codes and detailed backend messages (from detail or message properties).
  • Verb Wrappers: Provides standard typing and configurations for get(), post(), put(), and delete().
export class ApiError extends Error {
status: number;
constructor(status: number, message: string) {
super(message);
this.status = status;
this.name = 'ApiError';
}
}

The favorites curation contract is defined in src/lib/api/favorites.ts. It exposes three core endpoints:

export interface FavoriteEntry {
normalized_fen: string;
dice_sorted: string;
moves: string[];
note: string | null;
}
export interface PositionFavorites {
normalized_fen: string;
items: FavoriteEntry[];
}
// GET /api/opening-book/favorites?fen=...
export function getFavorites(fen: string): Promise<PositionFavorites>;
// PUT /api/opening-book/favorites (body: { fen, dice, moves })
export function putFavorite(
fen: string,
dice: string,
moves: string[],
token: string,
): Promise<FavoriteEntry>;
// DELETE /api/opening-book/favorites (body: { fen, dice })
export function deleteFavorite(fen: string, dice: string, token: string): Promise<void>;

The application supports setting recommendations (favorites) for specific positions and rolled dice combinations. Curation permissions are gated via a Curator Token.

The token state, cache, and validation actions are managed via curatorStore.svelte.ts.

The token is cached locally inside the user’s browser localStorage using the key dc-curator-token. This ensures curators do not have to re-enter their token on page reload.

const TOKEN_KEY = 'dc-curator-token';
class CuratorStore {
token = $state<string>('');
constructor() {
if (typeof window !== 'undefined') {
try {
this.token = localStorage.getItem(TOKEN_KEY) ?? '';
} catch (_e) {
// Ignored — privacy mode or storage unavailable
}
}
}
}

When a user navigates between chess positions rapidly, multiple asynchronous HTTP requests to getFavorites are fired in flight. To prevent late-arriving responses from overwriting the state of the currently active board position, CuratorStore employs a monotonic version counter:

class CuratorStore {
private version = 0;
private loadedFen = '';
async loadFavorites(fen: string) {
if (!this.hasToken || fen === this.loadedFen) return;
const myVersion = ++this.version;
try {
const data = await getFavorites(fen);
// Discard if a newer invalidate() or loadFavorites() was triggered in flight
if (myVersion !== this.version) return;
// Map data to reactive favMap...
} catch (_e) {
// Fail silently, leave favMap empty
}
}
}

[!TIP] When a user changes positions or the explorer component is unmounted, calling curatorStore.invalidate() increments the version counter and clears favMap. This immediately discards any active in-flight request and prevents memory leaks or state bleeding between positions.

When toggling favorites via toggleFavorite(), CuratorStore performs a pessimistic update. It awaits the backend API request (PUT or DELETE) to succeed before updating the reactive favMap in memory. If the API request fails (e.g., due to an expired token or network error), the map state remains unmodified, preventing the UI from showing incorrect recommendations.