All files / src/trpc/routers phase-dispatch.ts

67.85% Statements 19/28
70% Branches 7/10
40% Functions 2/5
67.85% Lines 19/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95                      11x                                                                 4x   4x 4x           4x             4x 4x   4x 15x                   15x 15x     4x 15x 11x 11x 12x 12x 12x           4x        
/**
 * Phase Dispatch Router — queue, dispatch, state, child tasks
 */
 
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import type { Task } from '../../db/schema.js';
import type { ProcedureBuilder } from '../trpc.js';
import { requirePhaseDispatchManager, requireTaskRepository } from './_helpers.js';
 
export function phaseDispatchProcedures(publicProcedure: ProcedureBuilder) {
  return {
    queuePhase: publicProcedure
      .input(z.object({ phaseId: z.string().min(1) }))
      .mutation(async ({ ctx, input }) => {
        const phaseDispatchManager = requirePhaseDispatchManager(ctx);
        await phaseDispatchManager.queuePhase(input.phaseId);
        return { success: true };
      }),
 
    dispatchNextPhase: publicProcedure
      .mutation(async ({ ctx }) => {
        const phaseDispatchManager = requirePhaseDispatchManager(ctx);
        return phaseDispatchManager.dispatchNextPhase();
      }),
 
    getPhaseQueueState: publicProcedure
      .query(async ({ ctx }) => {
        const phaseDispatchManager = requirePhaseDispatchManager(ctx);
        return phaseDispatchManager.getPhaseQueueState();
      }),
 
    createChildTasks: publicProcedure
      .input(z.object({
        parentTaskId: z.string().min(1),
        tasks: z.array(z.object({
          number: z.number().int().positive(),
          name: z.string().min(1),
          description: z.string(),
          type: z.enum(['auto', 'checkpoint:human-verify', 'checkpoint:decision', 'checkpoint:human-action']).default('auto'),
          dependencies: z.array(z.number().int().positive()).optional(),
        })),
      }))
      .mutation(async ({ ctx, input }) => {
        const taskRepo = requireTaskRepository(ctx);
 
        const parentTask = await taskRepo.findById(input.parentTaskId);
        Iif (!parentTask) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: `Parent task '${input.parentTaskId}' not found`,
          });
        }
        Iif (parentTask.category !== 'detail') {
          throw new TRPCError({
            code: 'BAD_REQUEST',
            message: `Parent task must have category 'detail', got '${parentTask.category}'`,
          });
        }
 
        const numberToId = new Map<number, string>();
        const created: Task[] = [];
 
        for (const taskInput of input.tasks) {
          const task = await taskRepo.create({
            parentTaskId: input.parentTaskId,
            phaseId: parentTask.phaseId,
            initiativeId: parentTask.initiativeId,
            name: taskInput.name,
            description: taskInput.description,
            type: taskInput.type,
            order: taskInput.number,
            status: 'pending',
          });
          numberToId.set(taskInput.number, task.id);
          created.push(task);
        }
 
        for (const taskInput of input.tasks) {
          if (taskInput.dependencies && taskInput.dependencies.length > 0) {
            const taskId = numberToId.get(taskInput.number)!;
            for (const depNumber of taskInput.dependencies) {
              const dependsOnTaskId = numberToId.get(depNumber);
              Eif (dependsOnTaskId) {
                await taskRepo.createDependency(taskId, dependsOnTaskId);
              }
            }
          }
        }
 
        return created;
      }),
  };
}