How the Trade Matching System Uses Supabase Edge Functions to Find Trading Partners
The trade matching system leverages a Supabase Edge Function named get-trading-partners to execute SQL queries that calculate compatibility scores between users based on card ownership, rarity preferences, and active trading status, returning ranked partner suggestions to the frontend.
The marcelpanse/tcg-pocket-collection-tracker repository implements a serverless trade matching architecture that uses Supabase edge functions to identify potential trading partners. This approach offloads computationally intensive collection comparisons to the edge, keeping the React frontend lightweight while maintaining secure database access.
Architecture Overview: Edge Functions and Frontend Integration
The system splits responsibilities between a Supabase Edge Function (server-side) and a frontend service (client-side). This separation ensures complex SQL joins and aggregations run close to the database in supabase/functions/get-trading-partners/index.ts, minimizing latency for users.
The frontend abstraction lives in frontend/src/services/trade/tradeService.ts and provides a typed wrapper around the edge function invocation. This pattern allows the single-page application to request trading partner suggestions via HTTP POST without exposing database credentials or handling raw SQL.
The get-trading-partners Edge Function Implementation
Located at supabase/functions/get-trading-partners/index.ts, the edge function establishes a PostgreSQL connection pool using the SUPABASE_DB_URL environment variable. It dynamically selects between two query strategies based on whether the user searches for general partners or targets a specific card.
Database Connection and Query Execution
The function initializes a postgres.Pool connection and prepares parameterized queries. After executing the appropriate SQL via connection.queryObject, it serializes the rows—converting trade_matches to numeric values—and returns JSON with CORS headers to allow cross-origin requests from the frontend.
// Conceptual flow from supabase/functions/get-trading-partners/index.ts
const connection = await pool.connect();
const result = await connection.queryObject(sqlQuery);
// Rows processed and returned as JSON with CORS headers
The All-Matches Query for General Partner Discovery
When invoked without a card_id parameter, the function executes all_matches_query. This query scans the 50 most recently updated public accounts that have active trading enabled. For each candidate, it calculates:
num_to_give: Cards the user owns in surplus that the partner needsnum_to_get: Cards the partner owns in surplus that the user needstrade_matches: The summed minimum of give/get counts per rarity, filtered bytrade_rarity_settings
The query returns friend_id, username, and the aggregated compatibility score, sorted by highest match potential.
The Single-Card Query for Targeted Trading
When a card_id is provided via the request body, the function switches to single_card_query. This optimized path filters exclusively for partners with complementary surplus/deficit relationships for that specific card's rarity. This targeted approach reduces database load and returns only relevant partners for users seeking particular cards to complete their collections.
Frontend Integration via tradeService.ts
The frontend service exposes getTradingPartners(email, cardId?), a typed wrapper that invokes the edge function using the Supabase JavaScript client.
// From frontend/src/services/trade/tradeService.ts
const { data, error } = await supabase.functions.invoke('get-trading-partners', {
method: 'POST',
body: { email, card_id: cardId },
});
The function returns TradePartners[] data for UI rendering. Errors are caught and re-thrown to allow components to display user-friendly messages in modals or notification systems.
Practical Usage Examples
import { getTradingPartners } from '@/services/trade/tradeService'
// Find general trading partners
async function showPartners(email: string) {
try {
const partners = await getTradingPartners(email)
console.log('Suggested partners:', partners)
} catch (e) {
console.error('Failed to fetch partners', e)
}
}
// Find partners for a specific card
async function showCardPartners(email: string, cardId: number) {
try {
const partners = await getTradingPartners(email, cardId)
console.log(`Partners for card ${cardId}:`, partners)
} catch (e) {
console.error('Failed to fetch card-specific partners', e)
}
}
Database Schema and Trade Compatibility Logic
The matching algorithm joins four core tables in the Supabase PostgreSQL database:
accounts: Stores user profiles, privacy settings, and active trading statuscard_amounts: Tracks individual card ownership quantities per usercards_list: Contains card metadata including rarity classificationstrade_rarity_settings: Defines which rarities users are willing to trade
The edge function queries these tables to compute compatibility by comparing ownership amounts against rarity-specific trade preferences. Public accounts with recent collection updates receive priority in the results, ensuring suggestions remain current and actionable.
Summary
- The
get-trading-partnersedge function insupabase/functions/get-trading-partners/index.tsexecutes SQL queries that calculate trade compatibility scores between users based on their collections. - Two query modes exist:
all_matches_queryfor general partner discovery across all cards, andsingle_card_queryfor targeted trading of specific cards. - The frontend invokes the function via
supabase.functions.invoke()infrontend/src/services/trade/tradeService.ts, passing the user's email and optionalcard_id. - Compatibility scores derive from comparing
num_to_giveandnum_to_getvalues filtered bytrade_rarity_settingsacross theaccounts,card_amounts, andcards_listtables. - CORS-enabled JSON responses ensure the edge function integrates securely with the React frontend while keeping database credentials protected server-side.
Frequently Asked Questions
How does the edge function calculate trade compatibility scores?
The function calculates trade_matches by summing the minimum values of num_to_give and num_to_get for each rarity tier, respecting each user's trade_rarity_settings. This represents the maximum number of mutually beneficial trades possible between two users based on current inventories and stated preferences for specific card rarities.
What is the difference between the all-matches and single-card queries?
all_matches_query scans the 50 most recently updated public accounts with active trading enabled, evaluating compatibility across the entire collection to find partners with the highest overall trade potential. single_card_query filters exclusively for partners who can provide or accept a specific card_id, optimizing for targeted searches when users need particular cards.
How does the frontend invoke the Supabase edge function?
The frontend uses the getTradingPartners function from frontend/src/services/trade/tradeService.ts, which calls supabase.functions.invoke('get-trading-partners', { method: 'POST', body: { email, card_id: cardId } }). This approach provides TypeScript safety through the TradePartners interface and centralizes error handling for consistent UI feedback.
What database tables power the trade matching algorithm?
The algorithm queries four primary tables: accounts (for user profiles and trading preferences), card_amounts (for ownership quantities), cards_list (for card metadata and rarities), and trade_rarity_settings (for rarity-specific trading rules). The edge function joins these tables to identify surplus/deficit relationships and calculate optimal trading partner suggestions.
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 →