"use strict"; /*********************************************************************************************************************************************************************** * File Name: taskPrerequisites.util.js * Type of Program: Utility * Description: Cycle detection for the explicit Task ↔ Task prerequisite graph * (task_prerequisites junction table). A task's prerequisite set is * only meaningful if the graph stays a DAG — this checks whether * persisting a proposed edge set would introduce a cycle. * * Author: Kenneth Obsequio (@lash0000) * Date Created: Jul. 17, 2026 ***********************************************************************************************************************************************************************/ const { TaskPrerequisite } = require("../../models/task/task.mdl"); /** * Would setting `taskId`'s prerequisites to `newPrereqIds` introduce a cycle? * * Loads every existing task_prerequisites edge for the task list, overlays the * proposed edges for `taskId` (replacing whatever it currently points to), then * DFS from `taskId` looking for a path back to itself. */ async function wouldCreateCycle(taskId, newPrereqIds, taskListId, transaction) { const { Task } = require("../../models/task/task.mdl"); const siblingTasks = await Task.findAll({ where: { task_list_id: taskListId }, attributes: ["task_id"], transaction, }); const siblingIds = siblingTasks.map((t) => t.task_id); const existingEdges = await TaskPrerequisite.findAll({ where: { task_id: siblingIds }, attributes: ["task_id", "prerequisite_task_id"], transaction, }); // adjacency: task_id -> Set(prerequisite_task_id) ("depends on") const adjacency = new Map(); for (const { task_id, prerequisite_task_id } of existingEdges) { if (task_id === taskId) continue; // overlay taskId's edges with the proposed set below if (!adjacency.has(task_id)) adjacency.set(task_id, new Set()); adjacency.get(task_id).add(prerequisite_task_id); } adjacency.set(taskId, new Set(newPrereqIds)); // DFS from taskId looking for a path back to taskId const visited = new Set(); const stack = [...adjacency.get(taskId)]; while (stack.length) { const current = stack.pop(); if (current === taskId) return true; if (visited.has(current)) continue; visited.add(current); for (const next of adjacency.get(current) ?? []) stack.push(next); } return false; } module.exports = { wouldCreateCycle };