Data Fetching Audit
Comprehensive audit of data fetching strategies across Eddy
Data Fetching Architecture Audit
Source: This page is based on docs/DATA_FETCHING_AUDIT.md
Last Updated: January 20, 2026
Status: ✅ Post-Refactor - Excellent Architecture
This document provides a comprehensive audit of data fetching strategies across Eddy's two primary domains: the Workflow Builder (level design) and Sessions (gameplay).
Executive Summary
Current State Assessment
| Domain | Grade | Status | Key Achievements |
|---|---|---|---|
| Session | A+ | ✅ Exemplary - Best-in-class implementation | 71% reduction in requests, zero node waterfalls |
| Builder | A- | ✅ Excellent - Clean with intentional tradeoffs | 27% reduction in requests, quest logs by design |
| Overall | A | ✅ Production-ready with excellent architecture | Clear patterns, optimal performance, maintainable |
Key Improvements Achieved
| Metric | Before Refactor | After Refactor | Improvement |
|---|---|---|---|
| Session Network Requests | 12 + 3N (N=stages) | 12 (constant) | 71% for 10 stages |
| Builder Network Requests | 11 parallel | 8 parallel | 27% reduction |
| Node-Level Waterfalls | N×3 queries | 0 queries | 100% eliminated |
| Redundant Fixture Fetches | Multiple | Zero | 100% eliminated |
Mental Model: The Three Categories
From the "game engine" mental model, our data follows three distinct categories:
Category 1: Workflow-Level Fixtures (The Level Architecture)
Purpose: Static, reusable definitions that structure the game itself.
Tables:
workflows- The game definitionpages- Individual rooms/locationspage_transitions- Corridors between roomsblocks,sections- Interactive objects and containersworkflow_roles- Character class definitionsstage_role_assignments- Abilities per room per classsheets,columns- Data collection structure
Characteristics:
- Immutable during gameplay
- Shared across all sessions
- Fetched once, cached aggressively
- Changes affect future runs, not current ones
Category 2: Session-Level Quest Logs (Progression State)
Purpose: Dynamic, session-specific tracking of progress.
Tables:
workflow_runs- The active campaign instanceworkflow_run_stages- Which locations activated/completedsession_role_assignments- Which users play which classessession_assignments- Whose turn, which roomsession_discussions- Player communication
Characteristics:
- Mutable during gameplay
- Session-scoped (one per workflow run)
- Read frequently, written less so
- Lightweight (mostly IDs and timestamps)
Category 3: Player Input Data (Gameplay Actions & Results)
Purpose: Actual data produced by players during interaction.
Tables:
cells- Individual data valuesrows- Complete recordscell_versions- History- File uploads, linked records
Characteristics:
- Created during gameplay
- Persists after quest completion
- High volume
- The actual product
Session Domain Architecture
Entry Point: Session Overview Page
File: pages/sessions/workflows/[workflowId]/[workflowRunId]/index.tsx
Status: ✅ Exemplary
// Category 1: Context
const { data: currentUser } = useGetCurrentUserAndGroups()
// Category 1: Fixtures - COMPLETE TREE
const { data: workflow } = useGetWorkflow(workflowId)
const {
pages = [],
pageTransitions = [],
roles: workflowRoles = []
} = workflow || {}
// Category 2: Quest Log
const { data: runStages = [] } =
useGetWorkflowRunStagesByWorkflowRunId(workflowRunId)Assessment:
✅ Perfect fixture handling - Entry point fetches workflow which contains the full tree
✅ Props, not fetches - Extracts fixtures from workflow and passes them down as props
✅ Minimal Category 2 data - Only fetches runStages at entry level
Session Component
File: components/session/Session.tsx
Status: ✅ Exemplary
// Category 3: Player Input (Lazy loaded)
const { data: cellsForWorkflowRun = [] } =
useGetCellsByWorkflowRun(workflowRunId, workflowId)
const { data: columns = [] } =
useGetColumnsByWorkflow(workflowId)Key Pattern: Heavy player data loaded lazily, only when needed for rendering.
Builder Domain Architecture
Entry Point: Builder Page
File: pages/builder/[workflowId]/index.tsx
Status: ✅ Excellent
// Category 1: Complete workflow tree
const { data: workflow } = useGetWorkflow(workflowId)
// Category 1: Sheets (if needed)
const { data: sheets = [] } = useGetSheetsByWorkflow(workflowId)Assessment:
✅ Single workflow fetch - Gets complete tree in one request
✅ Intentional sheet fetch - Separate query for sheets (design choice for flexibility)
✅ No waterfalls - All queries run in parallel
Best Practices & Patterns
1. Fixture Extraction Pattern
// ✅ GOOD: Extract from parent data
const { pages, pageTransitions, roles } = workflow || {}
// ❌ BAD: Redundant fetch
const { data: pages } = useGetPagesByWorkflow(workflowId)2. Consolidated State Hooks
// ✅ GOOD: Single hook for related data
const { data } = useGetSessionState(workflowRunId)
const { session, runStages, sessionAssignments } = data ?? {}
// ❌ BAD: Multiple dependent hooks
const { data: session } = useGetSession(workflowRunId)
const { data: runStages } = useGetRunStages(workflowRunId)3. Lazy Loading Heavy Data
// ✅ GOOD: Load cells only in component that needs them
function Session() {
const { data: cells } = useGetCellsByWorkflowRun(workflowRunId)
// ...
}
// ❌ BAD: Load cells at entry point
function SessionPage() {
const { data: cells } = useGetCellsByWorkflowRun(workflowRunId)
// Passed down but not used in many components
}4. Pre-computed Maps
// ✅ GOOD: Build maps once, use many times
const sessionDataMaps = useMemo(
() => createSessionDataMaps({ pages, runStages, sessionAssignments }),
[pages, runStages, sessionAssignments]
)
// ❌ BAD: Filter on every access
const assignments = allAssignments.filter(a => a.pageId === pageId)Performance Metrics
Session Domain
Before Refactor:
- 12 initial requests
- 3 additional requests per stage (N stages = 3N requests)
- Total for 10-stage workflow: 12 + 30 = 42 requests
After Refactor:
- 12 initial requests (constant)
- 0 additional requests per stage
- Total for 10-stage workflow: 12 requests
Improvement: 71% reduction
Builder Domain
Before Refactor:
- 11 parallel requests at entry point
- Multiple redundant fixture fetches
After Refactor:
- 8 parallel requests at entry point
- Zero redundant fetches
Improvement: 27% reduction
Future Opportunities
1. Builder State Consolidation
Create a useGetBuilderState() hook similar to useGetSessionState() to further consolidate builder queries.
2. Optimistic Updates
Implement optimistic updates for common mutations to improve perceived performance.
3. Prefetching
Add strategic prefetching for likely next actions (e.g., prefetch session data when hovering over session link).
4. Cache Warming
Pre-populate React Query cache with data from SSR/SSG where applicable.