Skip to content

Commit 0ca7c45

Browse files
authored
Merge pull request #75 from r4topunk/perf/feed-single-query
perf(feed): unified feedEvents query (5 queries → 1)
2 parents 65258bf + cabddaf commit 0ca7c45

6 files changed

Lines changed: 599 additions & 726 deletions

File tree

src/app/api/members/route.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { NextResponse } from "next/server";
21
import { unstable_cache } from "next/cache";
2+
import { NextResponse } from "next/server";
33
import { fetchFarcasterProfilesByAddress } from "@/services/farcaster";
44
import {
55
fetchActiveVotesForVoters,
66
fetchAllMembers,
77
fetchNonCanceledProposalsCount,
8-
fetchVotesCountForVoters,
9-
fetchVoteSupportForVoters,
8+
fetchVoterActivity,
109
type MemberListItem,
1110
} from "@/services/members";
1211

@@ -19,24 +18,22 @@ const getCachedMembers = unstable_cache(
1918
async (): Promise<MemberListItem[]> => {
2019
const members = await fetchAllMembers();
2120
const owners = members.map((m) => m.owner);
22-
const [
23-
votesCountResult,
24-
activeVotesResult,
25-
nonCanceledResult,
26-
voteSupportResult,
27-
farcasterResult,
28-
] = await Promise.allSettled([
29-
fetchVotesCountForVoters(owners),
30-
fetchActiveVotesForVoters(owners),
31-
fetchNonCanceledProposalsCount(),
32-
fetchVoteSupportForVoters(owners),
33-
fetchFarcasterProfilesByAddress(owners),
34-
]);
21+
const [voterActivityResult, activeVotesResult, nonCanceledResult, farcasterResult] =
22+
await Promise.allSettled([
23+
fetchVoterActivity(owners),
24+
fetchActiveVotesForVoters(owners),
25+
fetchNonCanceledProposalsCount(),
26+
fetchFarcasterProfilesByAddress(owners),
27+
]);
3528

36-
const votesCountMap = votesCountResult.status === "fulfilled" ? votesCountResult.value : {};
29+
const voterActivity =
30+
voterActivityResult.status === "fulfilled"
31+
? voterActivityResult.value
32+
: { counts: {}, support: {} };
33+
const votesCountMap = voterActivity.counts;
34+
const voteSupportMap = voterActivity.support;
3735
const activeVotesMap = activeVotesResult.status === "fulfilled" ? activeVotesResult.value : {};
3836
const nonCanceledCount = nonCanceledResult.status === "fulfilled" ? nonCanceledResult.value : 0;
39-
const voteSupportMap = voteSupportResult.status === "fulfilled" ? voteSupportResult.value : {};
4037
const farcasterProfiles = farcasterResult.status === "fulfilled" ? farcasterResult.value : {};
4138

4239
return members.map((m) => {

src/app/api/proposals/per-month/route.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NextRequest, NextResponse } from "next/server";
2-
import { SubgraphSDK } from "@buildeross/sdk";
3-
import { CHAIN, DAO_ADDRESSES } from "@/lib/config";
2+
import { DAO_ADDRESSES } from "@/lib/config";
3+
import { subgraphQuery } from "@/lib/subgraph";
44

55
export const dynamic = "force-dynamic";
66
export const revalidate = 300; // 5 minutes
@@ -9,6 +9,22 @@ export const revalidate = 300; // 5 minutes
99
let cache: { proposals: number[]; timestamp: number } | null = null;
1010
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
1111

12+
// Minimal query: only `timeCreated` — previous SDK call pulled full
13+
// proposal payload (description, calldatas, votes) per entry just to
14+
// bucket by month.
15+
const PROPOSAL_TIMESTAMPS_QUERY = `
16+
query ProposalTimestamps($dao: String!, $first: Int!) {
17+
proposals(
18+
where: { dao: $dao }
19+
first: $first
20+
orderBy: timeCreated
21+
orderDirection: desc
22+
) {
23+
timeCreated
24+
}
25+
}
26+
`;
27+
1228
export async function GET(request: NextRequest) {
1329
try {
1430
const months = parseInt(request.nextUrl.searchParams.get("months") || "12", 10);
@@ -22,14 +38,14 @@ export async function GET(request: NextRequest) {
2238
});
2339
}
2440

25-
// Fetch from subgraph (no RPC calls)
26-
const result = await SubgraphSDK.connect(CHAIN.id).proposals({
27-
where: { dao: DAO_ADDRESSES.token.toLowerCase() },
41+
const { proposals: rows } = await subgraphQuery<{
42+
proposals: Array<{ timeCreated: string }>;
43+
}>(PROPOSAL_TIMESTAMPS_QUERY, {
44+
dao: DAO_ADDRESSES.token.toLowerCase(),
2845
first: 500,
29-
skip: 0,
3046
});
3147

32-
const proposals = (result.proposals || []).map((p) => Number(p.timeCreated ?? 0));
48+
const proposals = (rows || []).map((p) => Number(p.timeCreated ?? 0));
3349

3450
// Update cache
3551
cache = { proposals, timestamp: now };
@@ -67,4 +83,3 @@ function groupByMonth(timestamps: number[], months: number) {
6783

6884
return result;
6985
}
70-

src/services/dao.ts

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,54 +7,57 @@ export type DaoStats = {
77
ownerCount: number;
88
};
99

10-
type DaoQuery = {
10+
export type DaoOverview = DaoStats & {
11+
totalAuctionSalesWei: bigint;
12+
};
13+
14+
type DaoOverviewQuery = {
1115
dao: {
1216
id: string;
1317
totalSupply: number;
1418
ownerCount: number;
19+
totalAuctionSales: string | null;
1520
} | null;
1621
};
1722

18-
const DAO_GQL = /* GraphQL */ `
19-
query Dao($id: ID!) {
23+
// Single query covering every `dao(id)` read currently scattered across
24+
// fetchDaoStats + fetchTotalAuctionSalesWei. React `cache()` dedupes
25+
// within a request so callers still see per-request memoization.
26+
const DAO_OVERVIEW_GQL = /* GraphQL */ `
27+
query DaoOverview($id: ID!) {
2028
dao(id: $id) {
2129
id
2230
totalSupply
2331
ownerCount
32+
totalAuctionSales
2433
}
2534
}
2635
`;
2736

28-
export const fetchDaoStats = cache(async (): Promise<DaoStats> => {
37+
function safeBigInt(value: string | null | undefined): bigint {
38+
try {
39+
return BigInt(value ?? "0");
40+
} catch {
41+
return BigInt(0);
42+
}
43+
}
44+
45+
export const fetchDaoOverview = cache(async (): Promise<DaoOverview> => {
2946
const id = DAO_ADDRESSES.token.toLowerCase();
30-
const data = await subgraphQuery<DaoQuery>(DAO_GQL, { id });
47+
const data = await subgraphQuery<DaoOverviewQuery>(DAO_OVERVIEW_GQL, { id });
3148
return {
3249
totalSupply: Number(data.dao?.totalSupply ?? 0),
3350
ownerCount: Number(data.dao?.ownerCount ?? 0),
51+
totalAuctionSalesWei: safeBigInt(data.dao?.totalAuctionSales),
3452
};
3553
});
3654

37-
type DaoSalesQuery = {
38-
dao: {
39-
totalAuctionSales: string;
40-
} | null;
41-
};
42-
43-
const DAO_TOTAL_AUCTION_SALES_GQL = /* GraphQL */ `
44-
query TotalAuctionSales($id: ID!) {
45-
dao(id: $id) {
46-
totalAuctionSales
47-
}
48-
}
49-
`;
55+
export const fetchDaoStats = cache(async (): Promise<DaoStats> => {
56+
const { totalSupply, ownerCount } = await fetchDaoOverview();
57+
return { totalSupply, ownerCount };
58+
});
5059

5160
export const fetchTotalAuctionSalesWei = cache(async (): Promise<bigint> => {
52-
const id = DAO_ADDRESSES.token.toLowerCase();
53-
const data = await subgraphQuery<DaoSalesQuery>(DAO_TOTAL_AUCTION_SALES_GQL, { id });
54-
const wei = data.dao?.totalAuctionSales ?? "0";
55-
try {
56-
return BigInt(wei);
57-
} catch {
58-
return BigInt(0);
59-
}
61+
const { totalAuctionSalesWei } = await fetchDaoOverview();
62+
return totalAuctionSalesWei;
6063
});

0 commit comments

Comments
 (0)