@@ -2,7 +2,7 @@ import { workflow } from '@sim/db/schema'
22import { generateId } from '@sim/utils/id'
33import { and , eq , isNull } from 'drizzle-orm'
44import type { DbOrTx } from '@/lib/db/types'
5- import { listDeployedWorkflows , readDeployedState } from '@/lib/workspaces/fork/copy/deploy-bridge'
5+ import type { DeployedWorkflowSummary } from '@/lib/workspaces/fork/copy/deploy-bridge'
66import type { ForkEdge } from '@/lib/workspaces/fork/lineage/lineage'
77import { detectForkCascadeReferences } from '@/lib/workspaces/fork/mapping/cascade'
88import { buildForkResolver , getEdgeMappingRows } from '@/lib/workspaces/fork/mapping/mapping-store'
@@ -13,6 +13,7 @@ import {
1313 type ForkReferenceResolver ,
1414 scanWorkflowReferences ,
1515} from '@/lib/workspaces/fork/remap/remap-references'
16+ import type { WorkflowState } from '@/stores/workflows/workflow/types'
1617
1718export interface ForkPromotePlanItem {
1819 sourceWorkflowId : string
@@ -100,8 +101,23 @@ export async function computeForkPromotePlan(params: {
100101 sourceWorkspaceId : string
101102 targetWorkspaceId : string
102103 direction : 'push' | 'pull'
104+ /**
105+ * Source deployed workflows + their states, read by the caller BEFORE its
106+ * transaction (see `loadSourceDeployedStates`) so the plan never checks out a
107+ * second pooled connection from inside a tx.
108+ */
109+ deployedSourceWorkflows : DeployedWorkflowSummary [ ]
110+ sourceStates : Map < string , WorkflowState >
103111} ) : Promise < ForkPromotePlan > {
104- const { executor, edge, sourceWorkspaceId, targetWorkspaceId, direction } = params
112+ const {
113+ executor,
114+ edge,
115+ sourceWorkspaceId,
116+ targetWorkspaceId,
117+ direction,
118+ deployedSourceWorkflows,
119+ sourceStates,
120+ } = params
105121
106122 const mappingRows = await getEdgeMappingRows ( executor , edge . childWorkspaceId )
107123 const [ targetEnvKeys , sourceEnvKeys ] = await Promise . all ( [
@@ -118,8 +134,7 @@ export async function computeForkPromotePlan(params: {
118134 else identityMap . set ( row . childResourceId , row . parentResourceId )
119135 }
120136
121- const [ deployedSourceWorkflows , targetWorkflows , sourceWorkflowRows ] = await Promise . all ( [
122- listDeployedWorkflows ( executor , sourceWorkspaceId ) ,
137+ const [ targetWorkflows , sourceWorkflowRows ] = await Promise . all ( [
123138 executor
124139 . select ( { id : workflow . id , name : workflow . name , updatedAt : workflow . updatedAt } )
125140 . from ( workflow )
@@ -138,13 +153,12 @@ export async function computeForkPromotePlan(params: {
138153 // parent's originals; undeployed sources are simply skipped (target left as-is).
139154 const existingSourceIds = new Set ( sourceWorkflowRows . map ( ( w ) => w . id ) )
140155
141- // Build the items and scan references in one pass, loading each source's deployed
142- // state, scanning it, then discarding it - peak memory stays at one workflow state
143- // (the deployed-state cache, bounded globally, retains originals for the copy loop).
156+ // Build the items and scan references in one pass from the pre-read source states
157+ // (loaded before the caller's transaction; see loadSourceDeployedStates).
144158 const items : ForkPromotePlanItem [ ] = [ ]
145159 const referenceByKey = new Map < string , ForkReference > ( )
146160 for ( const source of deployedSourceWorkflows ) {
147- const sourceState = await readDeployedState ( source . id , sourceWorkspaceId )
161+ const sourceState = sourceStates . get ( source . id )
148162 if ( ! sourceState ) continue
149163
150164 const mappedTargetId = identityMap . get ( source . id )
@@ -180,12 +194,14 @@ export async function computeForkPromotePlan(params: {
180194 items,
181195 } )
182196
197+ const writtenTargetIds = new Set ( items . map ( ( item ) => item . targetWorkflowId ) )
183198 const archivedTargetIds : string [ ] = [ ]
184199 for ( const row of mappingRows ) {
185200 if ( row . resourceType !== 'workflow' || row . childResourceId == null ) continue
186201 const mappedSourceId = sourceIsParent ? row . parentResourceId : row . childResourceId
187202 const mappedTargetId = sourceIsParent ? row . childResourceId : row . parentResourceId
188203 if ( existingSourceIds . has ( mappedSourceId ) ) continue
204+ if ( writtenTargetIds . has ( mappedTargetId ) ) continue
189205 if ( targetActiveIds . has ( mappedTargetId ) ) archivedTargetIds . push ( mappedTargetId )
190206 }
191207 const archivedTargets = archivedTargetIds . map ( ( id ) => ( {
0 commit comments