Provider Interface
src/provider.ts defines the two provider interfaces that form the boundary between the API and its data sources. Route code depends only on these interfaces — never on a concrete implementation.
CardDataProvider
The main interface. All data access for card lookups, search, and sets goes through this contract.
interface CardDataProvider {
readonly sourceName: string;
warmup(): Promise<void>;
refresh(): Promise<void>;
getCardById(id: string): Promise<Card | null>;
getCardsByIds(ids: string[]): Promise<Card[]>;
searchByName(q: string, opts?: CardSearchOptions): Promise<Card[]>;
resolveRequest(req: CardRequest): Promise<ResolvedCard>;
getSets(): Promise<Array<{ setCode: string; setName: string; cardCount: number }>>;
getCardsBySet(setCode: string, opts?: { limit?: number }): Promise<Card[]>;
getRandomCard(): Promise<Card | null>;
getFormats(opts?: { includeInactive?: boolean }): Promise<Format[]>;
getCardLegalities(oracleKey: string, cardId: string): Promise<CardLegality[]>;
getCardRulings(oracleKey: string, cardId: string): Promise<CardRuling[]>;
getStats(): { lastRefresh: number; cardCount: number };
}
CardDataProvider methods
warmup()
Called once at app startup. Should:
- Load the card cache (fast path from Redis or cold load from Supabase).
- Schedule background refreshes.
In the ElysiaJS API, warmup() is called before the server begins accepting requests. Elysia's lifecycle hooks (onStart) provide the right place to call this.
refresh()
Pulls fresh data from the upstream source and rebuilds the in-memory index. Falls back to the existing cache if the upstream is unreachable. Called on a schedule by the provider itself after warmup().
getCardById(id)
Returns a single card by its stable UUID. Returns null if not found — never throws.
getCardsByIds(ids)
Returns many full cards in one round-trip, in the order the IDs were given. Unknown IDs are omitted rather than returned as null. Used by buildCardDetail() to expand related-card stubs for the card detail payload without fanning out into one request per card.
searchByName(q, opts?)
Full-text search with optional set/collector filters. Performs exact match first; falls back to autocomplete fuzzy ranking unless opts.fuzzy === false.
resolveRequest(req)
Resolves a structured CardRequest to the single best matching printing. Handles set/collector fallback internally. Never throws — returns { card: null, matchType: "not-found" } on miss.
getSets()
Returns all known sets with code, name, and card count. Providers that don't support set listing may return [].
getCardsBySet(setCode, opts?)
Returns cards in a set ordered by collector number.
getRandomCard()
Returns one random card from the index. Returns null if the index is empty.
getFormats(opts?)
Returns the admin-managed play formats in display order. Retired formats
(active: false) are omitted unless includeInactive is set.
getCardLegalities(oracleKey, cardId)
Resolves one printing's legality in every active format, so callers never
have to infer a missing entry. oracleKey selects the card-level statuses shared
by all printings and cardId the per-printing overrides; precedence is printing
→ oracle → default legal. Each entry's scope reports which layer decided it.
getCardRulings(oracleKey, cardId)
Returns the rulings and notes visible on one printing: those shared across the
oracle group (card_id null) plus any scoped to this printing, oldest first.
Both are keyed on oracle_key rather than the card id, so a ruling is authored
once and inherited by every printing — see Card Types for
the derivation. buildCardDetail treats both as supplementary: a failure is
logged and degrades to an empty array rather than failing the whole payload.
getStats()
Returns { lastRefresh: number, cardCount: number } for the /meta endpoint. lastRefresh is a Unix timestamp (seconds).
Factory
src/providers/index.ts exports createProvider(), which reads CARD_PROVIDER from the environment and returns the appropriate CardDataProvider. Only "supabase" is supported. See Supabase Provider for implementation details.