@@ -66,6 +66,7 @@ export interface ListPackagesOptions {
6666 ecosystem ?: string
6767 staleOnly : boolean
6868 unstewardedOnly : boolean
69+ busFactor1Only : boolean
6970 sortBy : 'name' | 'impact' | 'openVulns'
7071 sortDir : 'asc' | 'desc'
7172}
@@ -94,6 +95,11 @@ export async function listPackagesForApi(
9495 conditions . push ( `(s.status = 'unassigned' OR s.id IS NULL)` )
9596 }
9697
98+ if ( opts . busFactor1Only ) {
99+ // References pm_counts LATERAL join below — computed once, used in both WHERE and SELECT
100+ conditions . push ( `pm_counts.cnt = 1` )
101+ }
102+
97103 const where = `WHERE ${ conditions . join ( ' AND ' ) } `
98104
99105 // health is a v2 field — fall back to name sort
@@ -103,8 +109,8 @@ export async function listPackagesForApi(
103109 else sortExpr = 'LOWER(p.name)'
104110 const sortDir = opts . sortDir === 'desc' ? 'DESC' : 'ASC'
105111
106- params . limit = opts . pageSize
107- params . offset = ( opts . page - 1 ) * opts . pageSize
112+ // Separate paginated params from filter-only params used by the fallback COUNT query
113+ const queryParams = { ... params , limit : opts . pageSize , offset : ( opts . page - 1 ) * opts . pageSize }
108114
109115 const rows : PackageListRow [ ] = await qx . select (
110116 `
@@ -115,21 +121,42 @@ export async function listPackagesForApi(
115121 p.impact AS "criticalityScore",
116122 s.status AS "stewardshipStatus",
117123 COALESCE(ap_counts.cnt, 0) AS "openVulns",
118- (SELECT COUNT(*)::int FROM package_maintainers pm WHERE pm.package_id = p.id) AS "maintainerCount",
124+ pm_counts.cnt AS "maintainerCount",
119125 COUNT(*) OVER() AS total
120126 FROM packages p
121127 LEFT JOIN stewardships s ON s.package_id = p.id
122128 LEFT JOIN LATERAL (
123129 SELECT COUNT(*)::int AS cnt FROM advisory_packages WHERE package_id = p.id
124130 ) ap_counts ON true
131+ LEFT JOIN LATERAL (
132+ SELECT COUNT(*)::int AS cnt FROM package_maintainers pm WHERE pm.package_id = p.id
133+ ) pm_counts ON true
125134 ${ where }
126135 ORDER BY ${ sortExpr } ${ sortDir } NULLS LAST, p.purl ${ sortDir }
127136 LIMIT $(limit) OFFSET $(offset)
128137 ` ,
129- params ,
138+ queryParams ,
130139 )
131140
132- const total = rows . length > 0 ? parseInt ( rows [ 0 ] . total , 10 ) : 0
141+ let total : number
142+ if ( rows . length > 0 ) {
143+ total = parseInt ( rows [ 0 ] . total , 10 )
144+ } else {
145+ // Window function returns no rows when the page is beyond the result set.
146+ // Fall back to a separate COUNT so the caller always gets the real total.
147+ const countRow : { count : string } = await qx . selectOne (
148+ `SELECT COUNT(*)::text AS count
149+ FROM packages p
150+ LEFT JOIN stewardships s ON s.package_id = p.id
151+ LEFT JOIN LATERAL (
152+ SELECT COUNT(*)::int AS cnt FROM package_maintainers pm WHERE pm.package_id = p.id
153+ ) pm_counts ON true
154+ ${ where } ` ,
155+ params ,
156+ )
157+ total = parseInt ( countRow . count , 10 )
158+ }
159+
133160 return { rows, total }
134161}
135162
0 commit comments