@@ -316,6 +316,48 @@ function filterDeploymentVersions(
316316 } ) ;
317317}
318318
319+ const CEL_BATCH_SIZE = 500 ;
320+ const CEL_MAX_SCAN = 100_000 ;
321+
322+ async function listDeploymentVersionsMatchingCel (
323+ deploymentId : string ,
324+ cel : string ,
325+ order : "asc" | "desc" ,
326+ offset : number ,
327+ limit : number ,
328+ ) {
329+ const orderBy =
330+ order === "asc"
331+ ? asc ( schema . deploymentVersion . createdAt )
332+ : desc ( schema . deploymentVersion . createdAt ) ;
333+
334+ const pageEnd = offset + limit ;
335+ const items : ( typeof schema . deploymentVersion . $inferSelect ) [ ] = [ ] ;
336+ let scanned = 0 ;
337+ let matched = 0 ;
338+
339+ while ( scanned < CEL_MAX_SCAN ) {
340+ const batch = await db
341+ . select ( )
342+ . from ( schema . deploymentVersion )
343+ . where ( eq ( schema . deploymentVersion . deploymentId , deploymentId ) )
344+ . orderBy ( orderBy )
345+ . limit ( CEL_BATCH_SIZE )
346+ . offset ( scanned ) ;
347+ if ( batch . length === 0 ) break ;
348+
349+ for ( const version of filterDeploymentVersions ( batch , cel ) ) {
350+ if ( matched >= offset && matched < pageEnd ) items . push ( version ) ;
351+ matched ++ ;
352+ }
353+
354+ scanned += batch . length ;
355+ if ( batch . length < CEL_BATCH_SIZE ) break ;
356+ }
357+
358+ return { items, total : matched } ;
359+ }
360+
319361const listDeploymentVersions : AsyncTypedHandler <
320362 "/v1/workspaces/{workspaceId}/deployments/{deploymentId}/versions" ,
321363 "get"
@@ -326,12 +368,12 @@ const listDeploymentVersions: AsyncTypedHandler<
326368 const order = req . query . order ?? "desc" ;
327369 const { cel } = req . query ;
328370
329- const orderBy =
330- order === "asc"
331- ? asc ( schema . deploymentVersion . createdAt )
332- : desc ( schema . deploymentVersion . createdAt ) ;
333-
334371 if ( cel == null ) {
372+ const orderBy =
373+ order === "asc"
374+ ? asc ( schema . deploymentVersion . createdAt )
375+ : desc ( schema . deploymentVersion . createdAt ) ;
376+
335377 const { total } = await db
336378 . select ( { total : count ( ) } )
337379 . from ( schema . deploymentVersion )
@@ -358,20 +400,17 @@ const listDeploymentVersions: AsyncTypedHandler<
358400 if ( ! validResourceSelector ( cel ) )
359401 throw new ApiError ( "Invalid CEL expression" , 400 ) ;
360402
361- // CEL is evaluated in-memory, so cap the candidate set to bound cost.
362- // Filtering applies to the 1000 most-recent (or oldest, for asc) versions.
363- const candidates = await db
364- . select ( )
365- . from ( schema . deploymentVersion )
366- . where ( eq ( schema . deploymentVersion . deploymentId , deploymentId ) )
367- . orderBy ( orderBy )
368- . limit ( 1000 ) ;
369-
370- const filtered = filterDeploymentVersions ( candidates , cel ) ;
403+ const { items, total } = await listDeploymentVersionsMatchingCel (
404+ deploymentId ,
405+ cel ,
406+ order ,
407+ offset ,
408+ limit ,
409+ ) ;
371410
372411 res . status ( 200 ) . json ( {
373- items : filtered . slice ( offset , offset + limit ) . map ( formatDeploymentVersion ) ,
374- total : filtered . length ,
412+ items : items . map ( formatDeploymentVersion ) ,
413+ total,
375414 limit,
376415 offset,
377416 } ) ;
0 commit comments