LobeHub Data Fetching Architecture
Related Skills:
store-data-structures - How to structure List and Detail data in stores (Map vs Array patterns)
Architecture Overview
βββββββββββββββ
β Component β
ββββββββ¬βββββββ
β 1. Call useFetchXxx hook from store
β
ββββββββββββββββββββ
β Zustand Store β
β (State + Hook) β
ββββββββ¬ββββββββββββ
β 2. useClientDataSWR calls service
β
ββββββββββββββββββββ
β Service Layer β
β (xxxService) β
ββββββββ¬ββββββββββββ
β 3. Call lambdaClient
β
ββββββββββββββββββββ
β lambdaClient β
β (TRPC Client) β
ββββββββββββββββββββ
Core Principles
β
DO
- Use Service Layer for all API calls
- Use Store SWR Hooks for data fetching (not useEffect)
- Use proper data structures - See
store-data-structures skill for List vs Detail patterns
- Use lambdaClient.mutate for write operations (create/update/delete)
- Use lambdaClient.query only inside service methods
β DON'T
- Never use useEffect for data fetching
- Never call lambdaClient directly in components or stores
- Never use useState for server data
- Never mix data structure patterns - Follow
store-data-structures skill
Note: For data structure patterns (Map vs Array, List vs Detail), see the store-data-structures skill.
Layer 1: Service Layer
Purpose
- Encapsulate all API calls to lambdaClient
- Provide clean, typed interfaces
- Single source of truth for API operations
Service Structure
import { lambdaClient } from '@/libs/trpc/client';
class AgentEvalService {
async listBenchmarks() {
return lambdaClient.agentEval.listBenchmarks.query();
}
async getBenchmark(id: string) {
return lambdaClient.agentEval.getBenchmark.query({ id });
}
async createBenchmark(params: CreateBenchmarkParams) {
return lambdaClient.agentEval.createBenchmark.mutate(params);
}
async updateBenchmark(params: UpdateBenchmarkParams) {
return lambdaClient.agentEval.updateBenchmark.mutate(params);
}
async deleteBenchmark(id: string) {
return lambdaClient.agentEval.deleteBenchmark.mutate({ id });
}
}
export const agentEvalService = new AgentEvalService();
Service Guidelines
- One service per domain (e.g., agentEval, ragEval, aiAgent)
- Export singleton instance (
export const xxxService = new XxxService())
- Method names match operations (list, get, create, update, delete)
- Clear parameter types (use interfaces for complex params)
Layer 2: Store with SWR Hooks
Purpose
- Manage client-side state
- Provide SWR hooks for data fetching
- Handle cache invalidation
Data Structure: See store-data-structures skill for how to structure List and Detail data.
Store Structure Overview
import type { AgentEvalBenchmark, AgentEvalBenchmarkListItem } from '@lobechat/types';
export interface BenchmarkSliceState {
benchmarkList: AgentEvalBenchmarkListItem[];
benchmarkListInit: boolean;
benchmarkDetailMap: Record<string, AgentEvalBenchmark>;
loadingBenchmarkDetailIds: string[];
isCreatingBenchmark: boolean;
isUpdatingBenchmark: boolean;
isDeletingBenchmark: boolean;
}
For complete initialState, reducer, and internal dispatch patterns, see the store-data-structures skill.
Create Actions
import type { SWRResponse } from 'swr';
import type { StateCreator } from 'zustand/vanilla';
import isEqual from 'fast-deep-equal';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { agentEvalService } from '@/services/agentEval';
import type { EvalStore } from '@/store/eval/store';
import { benchmarkDetailReducer, type BenchmarkDetailDispatch } from './reducer';
const FETCH_BENCHMARKS_KEY = 'FETCH_BENCHMARKS';
const FETCH_BENCHMARK_DETAIL_KEY = 'FETCH_BENCHMARK_DETAIL';
export interface BenchmarkAction {
useFetchBenchmarks: () => SWRResponse;
useFetchBenchmarkDetail: (id?: string) => SWRResponse;
refreshBenchmarks: () => Promise<void>;
refreshBenchmarkDetail: (id: string) => Promise<void>;
createBenchmark: (params: CreateParams) => Promise<any>;
updateBenchmark: (params: UpdateParams) => Promise<void>;
deleteBenchmark: (id: string) => Promise<void>;
internal_dispatchBenchmarkDetail: (payload: BenchmarkDetailDispatch) => void;
internal_updateBenchmarkDetailLoading: (id: string, loading: boolean) => void;
}
export const createBenchmarkSlice: StateCreator<
EvalStore,
[['zustand/devtools', never]],
[],
BenchmarkAction
> = (set, get) => ({
useFetchBenchmarks: () => {
return useClientDataSWR(FETCH_BENCHMARKS_KEY, () => agentEvalService.listBenchmarks(), {
onSuccess: (data: any) => {
set(
{
benchmarkList: data,
benchmarkListInit: true,
},
false,
'useFetchBenchmarks/success',