Skip to content

Commit a4bb3cf

Browse files
aster-voidclaude
andcommitted
treewide: refactor code smells and optimize stats API
- Fix getMemberById to include projectMembers relations (consistency with getMemberBySlug) - Add comment to fire-and-forget view count explaining intentional design - Extract search-modal-base.svelte to deduplicate admin/site search modals (42% code reduction) - Consolidate getMembers exports: single source in members.remote.ts, re-export from articles/projects - Remove getAuthors alias, use getMembers consistently - Optimize stats API: use COUNT(*) and LIMIT at DB level instead of fetching all records 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent db3b150 commit a4bb3cf

File tree

12 files changed

+587
-529
lines changed

12 files changed

+587
-529
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Stats API Optimization
2+
3+
## Date
4+
5+
2025-12-18
6+
7+
## Changes
8+
9+
### Before
10+
11+
The stats API was fetching ALL records with full relations, then filtering/counting in memory:
12+
13+
```typescript
14+
// ❌ Inefficient
15+
const [members, articles, projects] = await Promise.all([
16+
listMembers(), // Fetches all members
17+
listAllArticles(), // Fetches all articles with author relations
18+
listProjects(), // Fetches all projects with projectMembers relations
19+
]);
20+
21+
const publishedCount = articles.filter((a) => a.published).length;
22+
const recentArticles = [...articles]
23+
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
24+
.slice(0, 5);
25+
```
26+
27+
### After
28+
29+
Now using database-level aggregation and LIMIT clauses:
30+
31+
```typescript
32+
// ✅ Optimized
33+
const [
34+
membersCount,
35+
articlesCount,
36+
publishedArticlesCount,
37+
projectsCount,
38+
recentArticles,
39+
recentProjects,
40+
draftArticles,
41+
] = await Promise.all([
42+
countMembers(), // COUNT(*) query
43+
countArticles(), // COUNT(*) query
44+
countPublishedArticles(), // COUNT(*) with WHERE published = true
45+
countProjects(), // COUNT(*) query
46+
getRecentArticles(5), // ORDER BY updatedAt DESC LIMIT 5
47+
getRecentProjects(3), // ORDER BY updatedAt DESC LIMIT 3
48+
getRecentDraftArticles(3), // WHERE published = false LIMIT 3
49+
]);
50+
```
51+
52+
## Performance Impact
53+
54+
### Memory Usage
55+
56+
- Before: Loads ALL records into memory (could be hundreds or thousands)
57+
- After: Only loads the exact data needed (max 11 records for display items)
58+
59+
### Database Queries
60+
61+
- Before: 3 queries fetching everything with relations
62+
- After: 7 queries using efficient COUNT(\*) and LIMIT
63+
64+
### Network Transfer
65+
66+
- Before: Transfers full objects with all fields and relations
67+
- After: Transfers only count numbers and minimal fields for display
68+
69+
## Files Modified
70+
71+
1. `/src/lib/server/database/members.server.ts`
72+
- Added `countMembers()` - returns count using `COUNT(*)`
73+
74+
2. `/src/lib/server/database/articles.server.ts`
75+
- Added `countArticles()` - returns total article count
76+
- Added `countPublishedArticles()` - returns published article count
77+
- Added `getRecentArticles(limit)` - returns recent articles with LIMIT
78+
- Added `getRecentDraftArticles(limit)` - returns recent drafts with LIMIT
79+
80+
3. `/src/lib/server/database/projects.server.ts`
81+
- Added `countProjects()` - returns count using `COUNT(*)`
82+
- Added `getRecentProjects(limit)` - returns recent projects with LIMIT
83+
84+
4. `/src/lib/data/private/stats.remote.ts`
85+
- Updated to use new optimized functions
86+
- Maintains same data shape returned to frontend
87+
88+
## Testing
89+
90+
Created `/src/lib/server/database/stats.test.ts` with tests verifying:
91+
92+
- All count functions return numbers ≥ 0
93+
- All "recent" functions respect limit constraints
94+
- Array return types are correct
95+
96+
All tests passing: 7/7 ✅
97+
98+
## Type Safety
99+
100+
All changes maintain full TypeScript type safety with no `as` or `any` usage.

0 commit comments

Comments
 (0)