11import { isWaitpointOutputTimeout , prettyPrintPacket } from "@trigger.dev/core/v3" ;
2- import {
3- DATABASE_SCHEMA ,
4- type PrismaClientOrTransaction ,
5- type PrismaReplicaClient ,
6- } from "~/db.server" ;
2+ import { type PrismaClientOrTransaction , type PrismaReplicaClient } from "~/db.server" ;
73import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server" ;
84import { generateHttpCallbackUrl } from "~/services/httpCallback.server" ;
95import { logger } from "~/services/logger.server" ;
@@ -15,10 +11,12 @@ import { waitpointStatusToApiStatus } from "./WaitpointListPresenter.server";
1511
1612export type WaitpointDetail = NonNullable < Awaited < ReturnType < WaitpointPresenter [ "call" ] > > > ;
1713
18- // Single-sourced bound for connected run friendlyIds: applied at the FETCH in #connectedRunIdsOn,
19- // not just at display time.
14+ // Single-sourced display bound for a waitpoint's connected run friendlyIds.
2015export const CONNECTED_RUNS_DISPLAY_LIMIT = 5 ;
2116
17+ // Over-read connection rows on the FK-free dedicated join so danglers don't cost a display slot.
18+ export const CONNECTED_RUNS_CONNECTION_SCAN_LIMIT = CONNECTED_RUNS_DISPLAY_LIMIT * 5 ;
19+
2220export class WaitpointPresenter extends BasePresenter {
2321 constructor (
2422 prisma ?: PrismaClientOrTransaction ,
@@ -84,8 +82,7 @@ export class WaitpointPresenter extends BasePresenter {
8482
8583 // Connected-run friendlyIds gathered across BOTH stores. The run<->waitpoint join co-locates with
8684 // the RUN (written on the run's DB), so the waitpoint's own store misses a cross-DB connection; we
87- // read the join on each client and resolve the run's friendlyId on that same client, then union.
88- // We never relation-select `connectedRuns`: it is not a field on the dedicated subset `Waitpoint`.
85+ // read the join on each client, resolve the run's friendlyId on that same client, and union.
8986 async #connectedRunFriendlyIds( waitpointId : string ) : Promise < string [ ] > {
9087 const replica = this . _replica as unknown as PrismaReplicaClient ;
9188 const rawClients : PrismaReplicaClient [ ] =
@@ -99,17 +96,8 @@ export class WaitpointPresenter extends BasePresenter {
9996
10097 const friendlyIds = new Set < string > ( ) ;
10198 for ( const client of clients ) {
102- const runIds = await this . #connectedRunIdsOn( client , waitpointId ) ;
103- if ( runIds . length === 0 ) {
104- continue ;
105- }
106- const runs = await client . taskRun . findMany ( {
107- where : { id : { in : runIds } } ,
108- select : { friendlyId : true } ,
109- take : CONNECTED_RUNS_DISPLAY_LIMIT ,
110- } ) ;
111- for ( const run of runs ) {
112- friendlyIds . add ( run . friendlyId ) ;
99+ for ( const friendlyId of await this . #connectedRunFriendlyIdsOn( client , waitpointId ) ) {
100+ friendlyIds . add ( friendlyId ) ;
113101 }
114102 if ( friendlyIds . size >= CONNECTED_RUNS_DISPLAY_LIMIT ) {
115103 break ;
@@ -118,44 +106,56 @@ export class WaitpointPresenter extends BasePresenter {
118106 return Array . from ( friendlyIds ) . slice ( 0 , CONNECTED_RUNS_DISPLAY_LIMIT ) ;
119107 }
120108
121- // Schema-aware read of the run ids linked to a waitpoint: the dedicated subset uses the explicit
122- // `WaitpointRunConnection` model (scalar `taskRunId`, no FK -- a row can dangle after its run is
123- // deleted), the control-plane full schema the implicit `_WaitpointRunConnections` M2M
124- // (A = TaskRun.id, B = Waitpoint.id). Both branches existence-filter AT THE QUERY via a JOIN to
125- // TaskRun, so a dangling connection row can never occupy a LIMIT slot ahead of a real one.
126- // Tables are schema-qualified with DATABASE_SCHEMA (trusted boot constant) so a non-`public`
127- // schema= deployment resolves the right tables instead of leaning on search_path. $queryRawUnsafe,
128- // not a `sqlDatabaseSchema` Prisma.Sql fragment: `client` may be the dedicated run-ops client (a
129- // different Prisma runtime) which would bind a foreign runtime's Sql fragment as a param instead
130- // of inlining it. waitpointId and the constant limit stay bound params ($1/$2).
131- async #connectedRunIdsOn( client : PrismaReplicaClient , waitpointId : string ) : Promise < string [ ] > {
132- const isDedicated = Boolean (
133- ( client as unknown as { waitpointRunConnection ?: unknown } ) . waitpointRunConnection
134- ) ;
135-
136- if ( isDedicated ) {
137- const rows = await client . $queryRawUnsafe < { taskRunId : string } [ ] > (
138- `SELECT c."taskRunId" AS "taskRunId"
139- FROM ${ DATABASE_SCHEMA } ."WaitpointRunConnection" c
140- JOIN ${ DATABASE_SCHEMA } ."TaskRun" t ON t."id" = c."taskRunId"
141- WHERE c."waitpointId" = $1
142- LIMIT $2` ,
143- waitpointId ,
144- CONNECTED_RUNS_DISPLAY_LIMIT
145- ) ;
146- return rows . map ( ( row ) => row . taskRunId ) ;
109+ // Connected-run friendlyIds for one store, via the ORM. Two indexed reads joined in memory instead
110+ // of a SQL JOIN onto the (very large) TaskRun table: `id IN (...)` can only plan as a PK lookup, so
111+ // the planner can never scan TaskRun.
112+ //
113+ // Dedicated subset: the explicit `WaitpointRunConnection` is scalar (`taskRunId`, no FK), so a
114+ // connection can outlive a deleted run. We over-read connection ids, resolve runs by id (a missing
115+ // id just drops out -- danglers cost no display slot), and cap at the display limit.
116+ //
117+ // Control-plane full schema: no queryable join delegate (implicit M2M), so we traverse the
118+ // `connectedRuns` relation; it cascade-deletes with the run, so no dangler can exist.
119+ async #connectedRunFriendlyIdsOn(
120+ client : PrismaReplicaClient ,
121+ waitpointId : string
122+ ) : Promise < string [ ] > {
123+ const dedicated = (
124+ client as unknown as {
125+ waitpointRunConnection ?: {
126+ findMany : ( args : unknown ) => Promise < { taskRunId : string } [ ] > ;
127+ } ;
128+ }
129+ ) . waitpointRunConnection ;
130+
131+ if ( dedicated ) {
132+ const connections = await dedicated . findMany ( {
133+ where : { waitpointId } ,
134+ select : { taskRunId : true } ,
135+ take : CONNECTED_RUNS_CONNECTION_SCAN_LIMIT ,
136+ } ) ;
137+ if ( connections . length === 0 ) {
138+ return [ ] ;
139+ }
140+ const runs = await client . taskRun . findMany ( {
141+ where : { id : { in : connections . map ( ( connection ) => connection . taskRunId ) } } ,
142+ select : { friendlyId : true } ,
143+ take : CONNECTED_RUNS_DISPLAY_LIMIT ,
144+ } ) ;
145+ return runs . map ( ( run ) => run . friendlyId ) ;
147146 }
148147
149- const rows = await client . $queryRawUnsafe < { A : string } [ ] > (
150- `SELECT c."A" AS "A"
151- FROM ${ DATABASE_SCHEMA } ."_WaitpointRunConnections" c
152- JOIN ${ DATABASE_SCHEMA } ."TaskRun" t ON t."id" = c."A"
153- WHERE c."B" = $1
154- LIMIT $2` ,
155- waitpointId ,
156- CONNECTED_RUNS_DISPLAY_LIMIT
157- ) ;
158- return rows . map ( ( row ) => row . A ) ;
148+ const waitpoint = ( await (
149+ client . waitpoint . findFirst as (
150+ args : unknown
151+ ) => Promise < { connectedRuns : { friendlyId : string } [ ] } | null >
152+ ) ( {
153+ where : { id : waitpointId } ,
154+ select : {
155+ connectedRuns : { select : { friendlyId : true } , take : CONNECTED_RUNS_DISPLAY_LIMIT } ,
156+ } ,
157+ } ) ) as { connectedRuns : { friendlyId : string } [ ] } | null ;
158+ return ( waitpoint ?. connectedRuns ?? [ ] ) . map ( ( run ) => run . friendlyId ) ;
159159 }
160160
161161 public async call ( {
0 commit comments