@@ -10,6 +10,7 @@ import type { SourceType, TagType } from "./schema";
1010import {
1111 alternatives ,
1212 appDownloads ,
13+ appSources ,
1314 apps ,
1415 appTags ,
1516 comparisonPairs ,
@@ -41,6 +42,83 @@ function desktopOnlyAppIds(db: DrizzleDB) {
4142 ) ;
4243}
4344
45+ // ─── Slim List Helper ────────────────────────────────────────────────
46+ // List pages only need card-relevant fields. Loading full sources
47+ // (with metadata/packageName) and all tags wastes rows.
48+
49+ const appCardColumns = {
50+ id : apps . id ,
51+ name : apps . name ,
52+ slug : apps . slug ,
53+ description : apps . description ,
54+ iconUrl : apps . iconUrl ,
55+ } ;
56+
57+ async function withSlimRelations (
58+ db : DrizzleDB ,
59+ appRows : { id : string } [ ] ,
60+ ) : Promise <
61+ {
62+ id : string ;
63+ name : string ;
64+ slug : string ;
65+ description : string | null ;
66+ iconUrl : string | null ;
67+ sources : { source : string ; url : string } [ ] ;
68+ tags : { name : string ; slug : string ; type : string } [ ] ;
69+ } [ ]
70+ > {
71+ if ( appRows . length === 0 ) return [ ] ;
72+
73+ const appIds = appRows . map ( ( a ) => a . id ) ;
74+
75+ const [ sources , platformTags ] = await Promise . all ( [
76+ db
77+ . select ( {
78+ appId : appSources . appId ,
79+ source : appSources . source ,
80+ url : appSources . url ,
81+ } )
82+ . from ( appSources )
83+ . where ( inArray ( appSources . appId , appIds ) ) ,
84+ db
85+ . select ( {
86+ appId : appTags . appId ,
87+ name : tags . name ,
88+ slug : tags . slug ,
89+ type : tags . type ,
90+ } )
91+ . from ( appTags )
92+ . innerJoin ( tags , eq ( appTags . tagId , tags . id ) )
93+ . where ( and ( inArray ( appTags . appId , appIds ) , eq ( tags . type , "platform" ) ) ) ,
94+ ] ) ;
95+
96+ const sourcesByApp = new Map < string , { source : string ; url : string } [ ] > ( ) ;
97+ for ( const s of sources ) {
98+ const arr = sourcesByApp . get ( s . appId ) ?? [ ] ;
99+ arr . push ( { source : s . source , url : s . url } ) ;
100+ sourcesByApp . set ( s . appId , arr ) ;
101+ }
102+
103+ const tagsByApp = new Map <
104+ string ,
105+ { name : string ; slug : string ; type : string } [ ]
106+ > ( ) ;
107+ for ( const t of platformTags ) {
108+ const arr = tagsByApp . get ( t . appId ) ?? [ ] ;
109+ arr . push ( { name : t . name , slug : t . slug , type : t . type } ) ;
110+ tagsByApp . set ( t . appId , arr ) ;
111+ }
112+
113+ return (
114+ appRows as ( ( typeof appRows ) [ number ] & Record < string , unknown > ) [ ]
115+ ) . map ( ( app ) => ( {
116+ ...( app as any ) ,
117+ sources : sourcesByApp . get ( app . id ) ?? [ ] ,
118+ tags : tagsByApp . get ( app . id ) ?? [ ] ,
119+ } ) ) ;
120+ }
121+
44122// ─── Types ──────────────────────────────────────────────────────────
45123
46124export type AppWithDetails = Awaited < ReturnType < typeof getAppBySlug > > ;
@@ -79,18 +157,15 @@ export async function listApps(
79157 conditions . push ( inArray ( apps . id , appIdsWithAllTags ) ) ;
80158 }
81159
82- const results = await db . query . apps . findMany ( {
83- where : and ( ... conditions ) ,
84- with : { sources : true , tags : { with : { tag : true } } } ,
85- limit ,
86- offset ,
87- orderBy : apps . name ,
88- } ) ;
160+ const rows = await db
161+ . select ( appCardColumns )
162+ . from ( apps )
163+ . where ( and ( ... conditions ) )
164+ . limit ( limit )
165+ . offset ( offset )
166+ . orderBy ( apps . name ) ;
89167
90- return results . map ( ( app ) => ( {
91- ...app ,
92- tags : app . tags . map ( ( at ) => at . tag ) ,
93- } ) ) ;
168+ return withSlimRelations ( db , rows ) ;
94169}
95170
96171export async function getAppBySlug ( db : DrizzleDB , slug : string ) {
@@ -200,20 +275,16 @@ export async function listTagsByType(db: DrizzleDB, type: TagType) {
200275}
201276
202277export async function listCategoriesWithApps ( db : DrizzleDB ) {
203- const rows = await db
278+ return db
204279 . select ( {
205280 id : tags . id ,
206281 name : tags . name ,
207282 slug : tags . slug ,
208283 type : tags . type ,
209284 } )
210285 . from ( tags )
211- . innerJoin ( appTags , eq ( tags . id , appTags . tagId ) )
212- . where ( eq ( tags . type , "category" ) )
213- . groupBy ( tags . id )
286+ . where ( and ( eq ( tags . type , "category" ) , sql `${ tags . appCount } > 0` ) )
214287 . orderBy ( tags . name ) ;
215-
216- return rows ;
217288}
218289
219290// ─── Tag / Category Page Queries ────────────────────────────────────
@@ -265,18 +336,15 @@ export async function listAppsByTag(
265336 . from ( appTags )
266337 . where ( eq ( appTags . tagId , tagRow [ 0 ] . id ) ) ;
267338
268- const results = await db . query . apps . findMany ( {
269- where : inArray ( apps . id , appIdsWithTag ) ,
270- with : { sources : true , tags : { with : { tag : true } } } ,
271- limit ,
272- offset ,
273- orderBy : apps . name ,
274- } ) ;
339+ const rows = await db
340+ . select ( appCardColumns )
341+ . from ( apps )
342+ . where ( inArray ( apps . id , appIdsWithTag ) )
343+ . limit ( limit )
344+ . offset ( offset )
345+ . orderBy ( apps . name ) ;
275346
276- return results . map ( ( app ) => ( {
277- ...app ,
278- tags : app . tags . map ( ( at ) => at . tag ) ,
279- } ) ) ;
347+ return withSlimRelations ( db , rows ) ;
280348}
281349
282350export async function listTagsWithCounts ( db : DrizzleDB , type ?: TagType ) {
@@ -288,12 +356,10 @@ export async function listTagsWithCounts(db: DrizzleDB, type?: TagType) {
288356 name : tags . name ,
289357 slug : tags . slug ,
290358 type : tags . type ,
291- appCount : sql < number > `count( ${ appTags . appId } )` ,
359+ appCount : tags . appCount ,
292360 } )
293361 . from ( tags )
294- . leftJoin ( appTags , eq ( tags . id , appTags . tagId ) )
295362 . where ( conditions . length ? and ( ...conditions ) : undefined )
296- . groupBy ( tags . id )
297363 . orderBy ( tags . name ) ;
298364}
299365
@@ -302,16 +368,18 @@ export async function listTagsWithCounts(db: DrizzleDB, type?: TagType) {
302368export async function searchApps ( db : DrizzleDB , query : string ) {
303369 const pattern = `%${ query } %` ;
304370
305- const [ appResults , propResults ] = await Promise . all ( [
306- db . query . apps . findMany ( {
307- where : and (
308- like ( apps . name , pattern ) ,
309- notInArray ( apps . id , desktopOnlyAppIds ( db ) ) ,
310- ) ,
311- with : { sources : true , tags : { with : { tag : true } } } ,
312- limit : 20 ,
313- orderBy : apps . name ,
314- } ) ,
371+ const [ appRows , propResults ] = await Promise . all ( [
372+ db
373+ . select ( appCardColumns )
374+ . from ( apps )
375+ . where (
376+ and (
377+ like ( apps . name , pattern ) ,
378+ notInArray ( apps . id , desktopOnlyAppIds ( db ) ) ,
379+ ) ,
380+ )
381+ . limit ( 20 )
382+ . orderBy ( apps . name ) ,
315383 db
316384 . select ( )
317385 . from ( proprietaryApps )
@@ -321,28 +389,22 @@ export async function searchApps(db: DrizzleDB, query: string) {
321389 ] ) ;
322390
323391 return {
324- apps : appResults . map ( ( app ) => ( {
325- ...app ,
326- tags : app . tags . map ( ( at ) => at . tag ) ,
327- } ) ) ,
392+ apps : await withSlimRelations ( db , appRows ) ,
328393 proprietaryApps : propResults ,
329394 } ;
330395}
331396
332397// ─── Discovery Queries ──────────────────────────────────────────────
333398
334399export async function getRecentApps ( db : DrizzleDB ) {
335- const results = await db . query . apps . findMany ( {
336- where : notInArray ( apps . id , desktopOnlyAppIds ( db ) ) ,
337- with : { sources : true , tags : { with : { tag : true } } } ,
338- orderBy : sql ` ${ apps . createdAt } desc` ,
339- limit : 20 ,
340- } ) ;
400+ const rows = await db
401+ . select ( appCardColumns )
402+ . from ( apps )
403+ . where ( notInArray ( apps . id , desktopOnlyAppIds ( db ) ) )
404+ . orderBy ( sql ` ${ apps . createdAt } desc` )
405+ . limit ( 20 ) ;
341406
342- return results . map ( ( app ) => ( {
343- ...app ,
344- tags : app . tags . map ( ( at ) => at . tag ) ,
345- } ) ) ;
407+ return withSlimRelations ( db , rows ) ;
346408}
347409
348410// ─── Desktop App Queries ────────────────────────────────────────────
@@ -366,18 +428,15 @@ export async function listDesktopApps(
366428 . where ( inArray ( appTags . tagId , desktopTagIds ) )
367429 . groupBy ( appTags . appId ) ;
368430
369- const results = await db . query . apps . findMany ( {
370- where : inArray ( apps . id , appsWithDesktopTag ) ,
371- with : { sources : true , tags : { with : { tag : true } } } ,
372- limit ,
373- offset ,
374- orderBy : apps . name ,
375- } ) ;
431+ const rows = await db
432+ . select ( appCardColumns )
433+ . from ( apps )
434+ . where ( inArray ( apps . id , appsWithDesktopTag ) )
435+ . limit ( limit )
436+ . offset ( offset )
437+ . orderBy ( apps . name ) ;
376438
377- return results . map ( ( app ) => ( {
378- ...app ,
379- tags : app . tags . map ( ( at ) => at . tag ) ,
380- } ) ) ;
439+ return withSlimRelations ( db , rows ) ;
381440}
382441
383442// ─── Scan Query ─────────────────────────────────────────────────────
@@ -569,18 +628,15 @@ export async function listAppsByLicense(
569628 const limit = Math . min ( rawLimit , 100 ) ;
570629 const offset = ( page - 1 ) * limit ;
571630
572- const results = await db . query . apps . findMany ( {
573- where : eq ( apps . license , license ) ,
574- with : { sources : true , tags : { with : { tag : true } } } ,
575- limit ,
576- offset ,
577- orderBy : apps . name ,
578- } ) ;
631+ const rows = await db
632+ . select ( appCardColumns )
633+ . from ( apps )
634+ . where ( eq ( apps . license , license ) )
635+ . limit ( limit )
636+ . offset ( offset )
637+ . orderBy ( apps . name ) ;
579638
580- return results . map ( ( app ) => ( {
581- ...app ,
582- tags : app . tags . map ( ( at ) => at . tag ) ,
583- } ) ) ;
639+ return withSlimRelations ( db , rows ) ;
584640}
585641
586642// ─── Sitemap Queries ────────────────────────────────────────────────
0 commit comments