How TCG Data Is Organized in CardsDB: Expansions, Rarity, and the Critical internal_id System
The TCG Pocket Collection Tracker centralizes all card metadata in frontend/src/lib/CardsDB.ts, using stable numeric internal_id values as immutable keys to link cards, expansions, and missions across the entire application.
The repository marcelpanse/tcg-pocket-collection-tracker implements a high-performance data layer that lazily loads JSON assets and constructs in-memory indexes for instant lookups. Understanding this TCG data organization is essential for contributors working with card collections, pack openings, or trade systems.
Data Architecture and Lookup Indexes
The CardsDB.ts file serves as the single source of truth for the frontend. It imports the master card list from cards.json and themed-collection mission files, then builds optimized dictionaries for O(1) access patterns.
Card Indexes and Lookup Dictionaries
Upon initialization, the module creates three critical lookup structures:
allCardsDict– Mapscard_idstrings (e.g.,"A1-001") to fullCardobjects for fast public-ID lookups.allCardsByInternalId– Maps numericinternal_idvalues toCardobjects, maintaining reverse-chronological order usingtoReversed().allCardsByInternalIdList– Groups cards sharing the sameinternal_id, enabling queries for alternate art variants or duplicate entries.
These dictionaries power the exported helper functions getCardById, getCardByInternalId, and getCardsByInternalId. The implementation at lines 22-25 of frontend/src/lib/CardsDB.ts ensures that UI components never scan the full card array.
Expansion Definitions and Metadata
The expansions array (lines 62-165) defines every set with precise metadata:
name– Machine-readable slug (e.g.,genetic-apex)id– Public expansion code (e.g.,A1,A2)internalId– Numeric stable identifier (e.g.,1,2,192)packs– Array of pack objects containingnameand UIcolorvaluesmissions– Imported mission arrays from JSON files likeA1-missions.json- Feature flags –
tradeable,openable,promobooleans packStructure– Object defining shiny pools, baby Pokémon presence, and cards-per-pack counts
Promo expansions occupy reserved high ranges (192, 193) and carry promo: true flags, physically separating them from core sets to prevent ID collisions.
Rarity and Crafting Cost Mappings
The database enumerates rarity tiers through two exported constants:
basicRarities– Array of the four standard diamond rarities (◆ to ◆◆◆◆)craftingCost– Dictionary mapping every rarity string (including stars and Crown Rare) to its crafting point cost
This structure decouples display logic from economic rules, allowing the UI to calculate deck costs without hardcoding values.
The Significance of the internal_id System
The internal_id field is not merely an alternative key—it is the backbone of data integrity throughout the application.
Stability Across Versions
Each expansion definition contains an explicit comment warning: // IMPORTANT note: these should NEVER EVER change. The internals of the DB depend on it. This immutability contract ensures that:
- Collection records remain valid after app updates
- URL parameters and shared links resolve correctly
- Backend edge functions can cache numeric IDs without invalidation risks
Performance and Cross-Entity References
Numeric internal_id values provide measurable advantages over string identifiers:
- Memory efficiency – Maps with integer keys consume less memory than string-keyed objects
- Query optimization – Supabase edge functions and query caches store
internal_idinstead of textualidto minimize payload size and eliminate case-sensitivity issues - Sorting consistency – The UI uses
internal_idto order expansions chronologically regardless of alphabetical naming changes
Duplicate Card Grouping
The internal_id system elegantly handles alternate artwork and promotional reprints. Cards sharing the same internal_id (accessed via getCardsByInternalId) represent functional duplicates, allowing the collection tracker to display "owned" status across variants while maintaining distinct card records for completion tracking.
Working with CardsDB: Practical Examples
Lookup by Public Card ID
Retrieve full card details using the human-readable identifier:
import { getCardById } from '@/lib/CardsDB'
const card = getCardById('A1-001')
if (card) {
console.log(`${card.name} is a ${card.rarity} from expansion ${card.expansion_id}`)
}
This utilizes the allCardsDict mapping (lines 26-28) for instant retrieval.
Query Expansion Metadata
Access pack structures and mission data through the expansion helper:
import { getExpansionById } from '@/lib/CardsDB'
const expansion = getExpansionById('A2')
console.log(`Expansion ${expansion.name} contains:`)
expansion.packs.forEach(pack => {
console.log(`- ${pack.name} (${pack.color})`)
})
The getExpansionById function (lines 34-40) returns the full Expansion object including the critical internalId property.
Find Duplicate Card Variants
Identify all cards sharing the same internal identifier, useful for alternate art detection:
import { getCardsByInternalId } from '@/lib/CardsDB'
const variants = getCardsByInternalId(42) ?? []
console.log(`Found ${variants.length} card variants for internal_id 42`)
This queries the allCardsByInternalIdList index (line 24) built during module initialization.
Calculate Crafting Costs
Determine the resource cost for any rarity tier:
import { craftingCost } from '@/lib/CardsDB'
function getCraftingPoints(rarity: string): number {
return craftingCost[rarity] ?? 0
}
console.log(`Crown Rare costs ${getCraftingPoints('Crown Rare')} points`)
Summary
frontend/src/lib/CardsDB.tsserves as the centralized data layer, lazily loadingcards.jsonand mission assets to build optimized lookup dictionaries.- The
internal_idsystem provides immutable numeric keys for expansions and cards, ensuring stable references across app versions and enabling fast Map-based lookups. - Lookup dictionaries (
allCardsDict,allCardsByInternalId,allCardsByInternalIdList) deliver O(1) access patterns for card queries and duplicate grouping. - Expansion metadata includes pack structures, mission arrays, and feature flags (
tradeable,promo), with promo sets occupying reserved high-valueinternal_idranges (192, 193). - Rarity handling separates display categories (
basicRarities) from economic rules (craftingCost), decoupling UI logic from game mechanics.
Frequently Asked Questions
Why does CardsDB use both card_id and internal_id?
The card_id (e.g., "A1-001") provides human-readable, public-facing identifiers suitable for URLs and user displays, while the numeric internal_id offers machine-optimized keys for internal lookups, sorting, and cross-referencing duplicate variants. This separation allows the public API to remain descriptive while the internal database maintains high-performance integer indexing.
What happens if an internal_id value changes?
Changing an internal_id would break the relationship between card records and their expansion metadata, causing runtime errors in getCardsByInternalId and invalidating user collection data. The codebase explicitly marks these values as immutable with comments stating they should "NEVER EVER change" because backend caches, URL parameters, and the allCardsByInternalId dictionary depend on these stable numeric keys.
How does the database handle promo cards and special expansions?
Promo expansions use high-range internal_id values (specifically 192 and 193 in the current implementation) and set the promo: true flag in their expansion definitions. This reserved numbering scheme ensures promotional sets never collide with future core expansions while still integrating into the same lookup dictionaries and mission systems as standard sets.
Where is rarity data defined and how is it used?
Rarity definitions live in the basicRarities array and craftingCost dictionary exported from frontend/src/lib/CardsDB.ts (lines 44-55). The craftingCost mapping specifically translates rarity strings (including "Crown Rare" and star rarities) into numeric crafting point values, enabling the deck builder and collection tracker to calculate resource requirements without hardcoding economic constants.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →