@@ -216,61 +216,61 @@ export const _upsertOrgFrameworkStructureCore = async ({
216216 ) ;
217217
218218 if ( policyTemplatesForCreation . length > 0 ) {
219+ // Pre-generate Policy and PolicyVersion IDs in a single round-trip so we can
220+ // skip the post-insert findMany lookups and the per-policy update loop.
221+ // Policy.currentVersionId -> PolicyVersion.id and PolicyVersion.policyId ->
222+ // Policy.id form an FK cycle, so we insert Policy first (currentVersionId null),
223+ // insert PolicyVersion, then set currentVersionId in one bulk UPDATE.
224+ const idPairs = await tx . $queryRaw <
225+ Array < { policy_id : string ; version_id : string } >
226+ > `
227+ SELECT
228+ generate_prefixed_cuid('pol'::text) AS policy_id,
229+ generate_prefixed_cuid('pv'::text) AS version_id
230+ FROM generate_series(1, ${ policyTemplatesForCreation . length } ::int)
231+ ` ;
232+ const preparedPolicies = policyTemplatesForCreation . map ( ( template , i ) => ( {
233+ template,
234+ policyId : idPairs [ i ] . policy_id ,
235+ versionId : idPairs [ i ] . version_id ,
236+ contentArray : extractTipTapContentArray ( template . content ) ,
237+ } ) ) ;
238+
219239 await tx . policy . createMany ( {
220- data : policyTemplatesForCreation . map ( ( policyTemplate ) => {
221- const templateContent = policyTemplate . content ;
222- const contentArray = extractTipTapContentArray ( templateContent ) ;
223- return {
224- name : policyTemplate . name ,
225- description : policyTemplate . description ,
226- department : policyTemplate . department ,
227- frequency : policyTemplate . frequency ,
228- content : { set : contentArray } ,
229- organizationId : organizationId ,
230- policyTemplateId : policyTemplate . id ,
231- } ;
232- } ) ,
240+ data : preparedPolicies . map ( ( { template, policyId, contentArray } ) => ( {
241+ id : policyId ,
242+ name : template . name ,
243+ description : template . description ,
244+ department : template . department ,
245+ frequency : template . frequency ,
246+ content : { set : contentArray } ,
247+ organizationId : organizationId ,
248+ policyTemplateId : template . id ,
249+ } ) ) ,
233250 } ) ;
234251
235- // Fetch newly created policies to create versions for them
236- const newlyCreatedPolicies = await tx . policy . findMany ( {
237- where : {
238- organizationId : organizationId ,
239- policyTemplateId : {
240- in : policyTemplatesForCreation . map ( ( t ) => t . id ) ,
241- } ,
242- } ,
243- select : { id : true , policyTemplateId : true , content : true } ,
252+ await tx . policyVersion . createMany ( {
253+ data : preparedPolicies . map ( ( { policyId, versionId, contentArray } ) => ( {
254+ id : versionId ,
255+ policyId,
256+ version : 1 ,
257+ content : { set : contentArray } ,
258+ changelog : 'Initial version from template' ,
259+ } ) ) ,
244260 } ) ;
245261
246- // Create version 1 for each newly created policy
247- if ( newlyCreatedPolicies . length > 0 ) {
248- await tx . policyVersion . createMany ( {
249- data : newlyCreatedPolicies . map ( ( policy ) => ( {
250- policyId : policy . id ,
251- version : 1 ,
252- content : { set : policy . content as Prisma . InputJsonValue [ ] } ,
253- changelog : 'Initial version from template' ,
254- } ) ) ,
255- } ) ;
256-
257- // Fetch the created versions to update policies with currentVersionId
258- const createdVersions = await tx . policyVersion . findMany ( {
259- where : {
260- policyId : { in : newlyCreatedPolicies . map ( ( p ) => p . id ) } ,
261- version : 1 ,
262- } ,
263- select : { id : true , policyId : true } ,
264- } ) ;
265-
266- // Update each policy with its currentVersionId
267- for ( const version of createdVersions ) {
268- await tx . policy . update ( {
269- where : { id : version . policyId } ,
270- data : { currentVersionId : version . id } ,
271- } ) ;
272- }
273- }
262+ const currentVersionValues = Prisma . join (
263+ preparedPolicies . map (
264+ ( { policyId, versionId } ) =>
265+ Prisma . sql `(${ policyId } ::text, ${ versionId } ::text)` ,
266+ ) ,
267+ ) ;
268+ await tx . $executeRaw `
269+ UPDATE "Policy"
270+ SET "currentVersionId" = v.version_id
271+ FROM (VALUES ${ currentVersionValues } ) AS v(policy_id, version_id)
272+ WHERE "Policy".id = v.policy_id
273+ ` ;
274274 }
275275
276276 /**
@@ -350,6 +350,8 @@ export const _upsertOrgFrameworkStructureCore = async ({
350350 ) ;
351351
352352 const requirementMapEntriesToCreate : Prisma . RequirementMapCreateManyInput [ ] = [ ] ;
353+ const controlToPolicyPairs : Array < { controlId : string ; policyId : string } > = [ ] ;
354+ const controlToTaskPairs : Array < { controlId : string ; taskId : string } > = [ ] ;
353355
354356 for ( const controlTemplateRelation of groupedControlTemplateRelations ) {
355357 const newControlId = controlTemplateIdToInstanceIdMap . get (
@@ -363,81 +365,86 @@ export const _upsertOrgFrameworkStructureCore = async ({
363365 continue ;
364366 }
365367
366- const updateData : Prisma . ControlUpdateInput = { } ;
367- let needsUpdate = false ;
368-
369368 // --- Process Requirements for RequirementMap ---
370- if ( controlTemplateRelation . requirementTemplateIds . length > 0 ) {
371- for ( const reqTemplateId of controlTemplateRelation . requirementTemplateIds ) {
372- let frameworkEditorFrameworkIdForReq : string | undefined ;
373- for ( const fw of frameworkEditorFrameworks ) {
374- if ( fw . requirements . some ( ( r ) => r . id === reqTemplateId ) ) {
375- frameworkEditorFrameworkIdForReq = fw . id ;
376- break ;
377- }
378- }
379- const frameworkInstanceId = frameworkEditorFrameworkIdForReq
380- ? editorFrameworkIdToInstanceIdMap . get ( frameworkEditorFrameworkIdForReq )
381- : undefined ;
382-
383- if ( frameworkInstanceId ) {
384- requirementMapEntriesToCreate . push ( {
385- controlId : newControlId ,
386- requirementId : reqTemplateId ,
387- frameworkInstanceId : frameworkInstanceId ,
388- } ) ;
389- } else {
390- console . warn (
391- `UpsertOrgFrameworkStructureCore: Could not find FrameworkInstanceId for editor requirement ID ${ reqTemplateId } . Cannot create RequirementMap for Control ${ newControlId } .` ,
392- ) ;
369+ for ( const reqTemplateId of controlTemplateRelation . requirementTemplateIds ) {
370+ let frameworkEditorFrameworkIdForReq : string | undefined ;
371+ for ( const fw of frameworkEditorFrameworks ) {
372+ if ( fw . requirements . some ( ( r ) => r . id === reqTemplateId ) ) {
373+ frameworkEditorFrameworkIdForReq = fw . id ;
374+ break ;
393375 }
394376 }
377+ const frameworkInstanceId = frameworkEditorFrameworkIdForReq
378+ ? editorFrameworkIdToInstanceIdMap . get ( frameworkEditorFrameworkIdForReq )
379+ : undefined ;
380+
381+ if ( frameworkInstanceId ) {
382+ requirementMapEntriesToCreate . push ( {
383+ controlId : newControlId ,
384+ requirementId : reqTemplateId ,
385+ frameworkInstanceId : frameworkInstanceId ,
386+ } ) ;
387+ } else {
388+ console . warn (
389+ `UpsertOrgFrameworkStructureCore: Could not find FrameworkInstanceId for editor requirement ID ${ reqTemplateId } . Cannot create RequirementMap for Control ${ newControlId } .` ,
390+ ) ;
391+ }
395392 }
396393
397- // --- Connect Policies ---
398- if ( controlTemplateRelation . policyTemplateIds . length > 0 ) {
399- const policiesToConnect = [ ] ;
400- for ( const policyTemplateId of controlTemplateRelation . policyTemplateIds ) {
401- const newPolicyId = policyTemplateIdToInstanceIdMap . get ( policyTemplateId ) ;
402- if ( newPolicyId ) {
403- policiesToConnect . push ( { id : newPolicyId } ) ;
404- } else {
405- console . warn (
406- `UpsertOrgFrameworkStructureCore: Policy instance not found for template ID ${ policyTemplateId } . Cannot connect to Control ${ newControlId } .` ,
407- ) ;
408- }
409- }
410- if ( policiesToConnect . length > 0 ) {
411- updateData . policies = { connect : policiesToConnect } ;
412- needsUpdate = true ;
394+ // --- Collect Control <-> Policy pairs ---
395+ for ( const policyTemplateId of controlTemplateRelation . policyTemplateIds ) {
396+ const newPolicyId = policyTemplateIdToInstanceIdMap . get ( policyTemplateId ) ;
397+ if ( newPolicyId ) {
398+ controlToPolicyPairs . push ( { controlId : newControlId , policyId : newPolicyId } ) ;
399+ } else {
400+ console . warn (
401+ `UpsertOrgFrameworkStructureCore: Policy instance not found for template ID ${ policyTemplateId } . Cannot connect to Control ${ newControlId } .` ,
402+ ) ;
413403 }
414404 }
415405
416- // --- Connect Tasks ---
417- if ( controlTemplateRelation . taskTemplateIds . length > 0 ) {
418- const tasksToConnect = [ ] ;
419- for ( const taskTemplateId of controlTemplateRelation . taskTemplateIds ) {
420- const newTaskId = taskTemplateIdToInstanceIdMap . get ( taskTemplateId ) ;
421- if ( newTaskId ) {
422- tasksToConnect . push ( { id : newTaskId } ) ;
423- } else {
424- console . warn (
425- `UpsertOrgFrameworkStructureCore: Task instance not found for template ID ${ taskTemplateId } . Cannot connect to Control ${ newControlId } .` ,
426- ) ;
427- }
428- }
429- if ( tasksToConnect . length > 0 ) {
430- updateData . tasks = { connect : tasksToConnect } ;
431- needsUpdate = true ;
406+ // --- Collect Control <-> Task pairs ---
407+ for ( const taskTemplateId of controlTemplateRelation . taskTemplateIds ) {
408+ const newTaskId = taskTemplateIdToInstanceIdMap . get ( taskTemplateId ) ;
409+ if ( newTaskId ) {
410+ controlToTaskPairs . push ( { controlId : newControlId , taskId : newTaskId } ) ;
411+ } else {
412+ console . warn (
413+ `UpsertOrgFrameworkStructureCore: Task instance not found for template ID ${ taskTemplateId } . Cannot connect to Control ${ newControlId } .` ,
414+ ) ;
432415 }
433416 }
417+ }
434418
435- if ( needsUpdate ) {
436- await tx . control . update ( {
437- where : { id : newControlId } ,
438- data : updateData ,
439- } ) ;
440- }
419+ // Bulk-insert into the implicit M2M join tables instead of N `control.update({ connect })`
420+ // calls. ON CONFLICT DO NOTHING preserves the idempotency the connect loop provided for
421+ // re-runs where some links already exist (e.g., adding a framework to an existing org).
422+ if ( controlToPolicyPairs . length > 0 ) {
423+ const rows = Prisma . join (
424+ controlToPolicyPairs . map (
425+ ( { controlId, policyId } ) =>
426+ Prisma . sql `(${ controlId } ::text, ${ policyId } ::text)` ,
427+ ) ,
428+ ) ;
429+ await tx . $executeRaw `
430+ INSERT INTO "_ControlToPolicy" ("A", "B")
431+ VALUES ${ rows }
432+ ON CONFLICT ("A", "B") DO NOTHING
433+ ` ;
434+ }
435+
436+ if ( controlToTaskPairs . length > 0 ) {
437+ const rows = Prisma . join (
438+ controlToTaskPairs . map (
439+ ( { controlId, taskId } ) =>
440+ Prisma . sql `(${ controlId } ::text, ${ taskId } ::text)` ,
441+ ) ,
442+ ) ;
443+ await tx . $executeRaw `
444+ INSERT INTO "_ControlToTask" ("A", "B")
445+ VALUES ${ rows }
446+ ON CONFLICT ("A", "B") DO NOTHING
447+ ` ;
441448 }
442449
443450 // --- Create RequirementMap entries ---
0 commit comments