@@ -94,6 +94,11 @@ export async function listPackagesForApi(
9494 conditions . push ( `(s.status = 'unassigned' OR s.id IS NULL)` )
9595 }
9696
97+ if ( opts . busFactor1Only ) {
98+ // References pm_counts LATERAL join below — computed once, used in both WHERE and SELECT
99+ conditions . push ( `pm_counts.cnt = 1` )
100+ }
101+
97102 const where = `WHERE ${ conditions . join ( ' AND ' ) } `
98103
99104 // health is a v2 field — fall back to name sort
@@ -103,8 +108,8 @@ export async function listPackagesForApi(
103108 else sortExpr = 'LOWER(p.name)'
104109 const sortDir = opts . sortDir === 'desc' ? 'DESC' : 'ASC'
105110
106- params . limit = opts . pageSize
107- params . offset = ( opts . page - 1 ) * opts . pageSize
111+ // Separate paginated params from filter-only params used by the fallback COUNT query
112+ const queryParams = { ... params , limit : opts . pageSize , offset : ( opts . page - 1 ) * opts . pageSize }
108113
109114 const rows : PackageListRow [ ] = await qx . select (
110115 `
@@ -115,21 +120,42 @@ export async function listPackagesForApi(
115120 p.impact AS "criticalityScore",
116121 s.status AS "stewardshipStatus",
117122 COALESCE(ap_counts.cnt, 0) AS "openVulns",
118- (SELECT COUNT(*)::int FROM package_maintainers pm WHERE pm.package_id = p.id) AS "maintainerCount",
123+ pm_counts.cnt AS "maintainerCount",
119124 COUNT(*) OVER() AS total
120125 FROM packages p
121126 LEFT JOIN stewardships s ON s.package_id = p.id
122127 LEFT JOIN LATERAL (
123128 SELECT COUNT(*)::int AS cnt FROM advisory_packages WHERE package_id = p.id
124129 ) ap_counts ON true
130+ LEFT JOIN LATERAL (
131+ SELECT COUNT(*)::int AS cnt FROM package_maintainers pm WHERE pm.package_id = p.id
132+ ) pm_counts ON true
125133 ${ where }
126134 ORDER BY ${ sortExpr } ${ sortDir } NULLS LAST, p.purl ${ sortDir }
127135 LIMIT $(limit) OFFSET $(offset)
128136 ` ,
129- params ,
137+ queryParams ,
130138 )
131139
132- const total = rows . length > 0 ? parseInt ( rows [ 0 ] . total , 10 ) : 0
140+ let total : number
141+ if ( rows . length > 0 ) {
142+ total = parseInt ( rows [ 0 ] . total , 10 )
143+ } else {
144+ // Window function returns no rows when the page is beyond the result set.
145+ // Fall back to a separate COUNT so the caller always gets the real total.
146+ const countRow : { count : string } = await qx . selectOne (
147+ `SELECT COUNT(*)::text AS count
148+ FROM packages p
149+ LEFT JOIN stewardships s ON s.package_id = p.id
150+ LEFT JOIN LATERAL (
151+ SELECT COUNT(*)::int AS cnt FROM package_maintainers pm WHERE pm.package_id = p.id
152+ ) pm_counts ON true
153+ ${ where } ` ,
154+ params ,
155+ )
156+ total = parseInt ( countRow . count , 10 )
157+ }
158+
133159 return { rows, total }
134160}
135161
0 commit comments