fix(types): flatten tRPC Zod schema types to resolve TS2589 inference depth errors

Cast Zod schemas with .refine()/.superRefine() to z.ZodType<InferredType> at the
procedure level. This short-circuits TypeScript's deep type recursion through
tRPC's middleware chain, eliminating 4 of 5 @ts-expect-error TS2589 suppressions
in web components (VacationModal, ProjectModal, UsersClient, CountriesClient).

Applied same pattern to allocation, timeline, staffing, dashboard, project, and
resource query/mutation procedures to reduce client-side type depth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 15:28:12 +02:00
parent 0d79f97d7a
commit 9bd3781c03
21 changed files with 460 additions and 304 deletions
@@ -1,7 +1,4 @@
import {
createAssignment,
updateAssignment,
} from "@capakraken/application";
import { createAssignment, updateAssignment } from "@capakraken/application";
import {
AllocationStatus,
CreateAllocationSchema,
@@ -10,6 +7,12 @@ import {
UpdateAllocationSchema,
UpdateAssignmentSchema,
} from "@capakraken/shared";
import type {
CreateAllocationInput,
CreateAssignmentInput,
UpdateAllocationInput,
UpdateAssignmentInput,
} from "@capakraken/shared";
import { z } from "zod";
import { findUniqueOrThrow } from "../../db/helpers.js";
import {
@@ -28,40 +31,36 @@ import {
ensureAssignmentRecord,
updateAllocationWithAudit,
} from "./assignment-mutations.js";
import {
managerProcedure,
requirePermission,
} from "../../trpc.js";
import { managerProcedure, requirePermission } from "../../trpc.js";
export const allocationAssignmentProcedures = {
create: managerProcedure
.input(CreateAllocationSchema)
.input(CreateAllocationSchema as z.ZodType<CreateAllocationInput>)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
const allocation = await ctx.db.$transaction(async (tx) =>
createAllocationReadModelEntry(
tx as Parameters<typeof createAssignment>[0],
input,
));
createAllocationReadModelEntry(tx as Parameters<typeof createAssignment>[0], input),
);
publishAllocationCreated(ctx.db, {
id: allocation.id,
projectId: allocation.projectId,
resourceId: allocation.resourceId,
}, { dispatchWebhook: true });
publishAllocationCreated(
ctx.db,
{
id: allocation.id,
projectId: allocation.projectId,
resourceId: allocation.resourceId,
},
{ dispatchWebhook: true },
);
return allocation;
}),
createAssignment: managerProcedure
.input(CreateAssignmentSchema)
.input(CreateAssignmentSchema as z.ZodType<CreateAssignmentInput>)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
const assignment = await ctx.db.$transaction(async (tx) => {
return createAssignment(
tx as unknown as Parameters<typeof createAssignment>[0],
input,
);
return createAssignment(tx as unknown as Parameters<typeof createAssignment>[0], input);
});
publishAllocationCreated(ctx.db, {
@@ -74,14 +73,16 @@ export const allocationAssignmentProcedures = {
}),
ensureAssignment: managerProcedure
.input(z.object({
resourceId: z.string(),
projectId: z.string(),
startDate: z.coerce.date(),
endDate: z.coerce.date(),
hoursPerDay: z.number().min(0.5).max(24),
role: z.string().optional(),
}))
.input(
z.object({
resourceId: z.string(),
projectId: z.string(),
startDate: z.coerce.date(),
endDate: z.coerce.date(),
hoursPerDay: z.number().min(0.5).max(24),
role: z.string().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
const result = await ensureAssignmentRecord(ctx.db, {
@@ -94,11 +95,15 @@ export const allocationAssignmentProcedures = {
});
if (result.action === "reactivated") {
publishAllocationUpdated(ctx.db, {
id: result.assignment.id,
projectId: result.assignment.projectId,
resourceId: result.assignment.resourceId,
}, { dispatchWebhook: true });
publishAllocationUpdated(
ctx.db,
{
id: result.assignment.id,
projectId: result.assignment.projectId,
resourceId: result.assignment.resourceId,
},
{ dispatchWebhook: true },
);
return result;
}
@@ -113,7 +118,12 @@ export const allocationAssignmentProcedures = {
}),
updateAssignment: managerProcedure
.input(z.object({ id: z.string(), data: UpdateAssignmentSchema }))
.input(
z.object({ id: z.string(), data: UpdateAssignmentSchema }) as z.ZodType<{
id: string;
data: UpdateAssignmentInput;
}>,
)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
const existing = await findUniqueOrThrow(
@@ -132,18 +142,27 @@ export const allocationAssignmentProcedures = {
);
});
publishAllocationUpdated(ctx.db, {
id: updated.id,
projectId: updated.projectId,
resourceId: updated.resourceId,
resourceIds: [existing.resourceId, updated.resourceId],
}, { dispatchWebhook: true });
publishAllocationUpdated(
ctx.db,
{
id: updated.id,
projectId: updated.projectId,
resourceId: updated.resourceId,
resourceIds: [existing.resourceId, updated.resourceId],
},
{ dispatchWebhook: true },
);
return updated;
}),
update: managerProcedure
.input(z.object({ id: z.string(), data: UpdateAllocationSchema }))
.input(
z.object({ id: z.string(), data: UpdateAllocationSchema }) as z.ZodType<{
id: string;
data: UpdateAllocationInput;
}>,
)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
const { existing, updated } = await updateAllocationWithAudit(ctx.db, input.id, input.data);
@@ -177,20 +196,18 @@ export const allocationAssignmentProcedures = {
return { success: true };
}),
delete: managerProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
const existing = await deleteAllocationWithAudit(ctx.db, input.id);
delete: managerProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
const existing = await deleteAllocationWithAudit(ctx.db, input.id);
publishAllocationDeleted(ctx.db, {
id: existing.entry.id,
projectId: existing.projectId,
resourceId: existing.entry.resourceId,
});
publishAllocationDeleted(ctx.db, {
id: existing.entry.id,
projectId: existing.projectId,
resourceId: existing.entry.resourceId,
});
return { success: true };
}),
return { success: true };
}),
batchDelete: managerProcedure
.input(z.object({ ids: z.array(z.string()).min(1).max(100) }))
@@ -198,11 +215,14 @@ export const allocationAssignmentProcedures = {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
const existing = await batchDeleteAllocationsWithAudit(ctx.db, input.ids);
publishBatchAllocationDeletes(ctx.db, existing.map((allocation) => ({
id: allocation.entry.id,
projectId: allocation.projectId,
resourceId: allocation.entry.resourceId,
})));
publishBatchAllocationDeletes(
ctx.db,
existing.map((allocation) => ({
id: allocation.entry.id,
projectId: allocation.projectId,
resourceId: allocation.entry.resourceId,
})),
);
return { count: existing.length };
}),
@@ -218,11 +238,14 @@ export const allocationAssignmentProcedures = {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
const updated = await batchUpdateAllocationStatusWithAudit(ctx.db, input);
publishBatchAllocationStatusUpdates(ctx.db, updated.map((allocation) => ({
id: allocation.id,
projectId: allocation.projectId,
resourceId: allocation.resourceId,
})));
publishBatchAllocationStatusUpdates(
ctx.db,
updated.map((allocation) => ({
id: allocation.id,
projectId: allocation.projectId,
resourceId: allocation.resourceId,
})),
);
return { count: updated.length };
}),
+32 -31
View File
@@ -1,3 +1,4 @@
import type { Prisma } from "@capakraken/db";
import {
deleteDemandRequirement,
fillOpenDemand,
@@ -10,6 +11,11 @@ import {
PermissionKey,
UpdateDemandRequirementSchema,
} from "@capakraken/shared";
import type {
CreateDemandRequirementInput,
FillDemandRequirementInput,
FillOpenDemandByAllocationInput,
} from "@capakraken/shared";
import { z } from "zod";
import { findUniqueOrThrow } from "../../db/helpers.js";
import {
@@ -25,41 +31,34 @@ import {
invalidateDashboardCacheInBackground,
} from "./effects.js";
import { DEMAND_INCLUDE } from "./shared.js";
import {
buildCreateDemandRequirementInput,
getDemandRequirementByIdOrThrow,
} from "./support.js";
import {
managerProcedure,
requirePermission,
} from "../../trpc.js";
import { buildCreateDemandRequirementInput, getDemandRequirementByIdOrThrow } from "./support.js";
import { managerProcedure, requirePermission } from "../../trpc.js";
export const allocationDemandProcedures = {
createDemandRequirement: managerProcedure
.input(CreateDemandRequirementSchema)
.input(CreateDemandRequirementSchema as z.ZodType<CreateDemandRequirementInput>)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
return createDemandRequirementWithEffects(ctx.db, input);
}),
createDemand: managerProcedure
.input(z.object({
projectId: z.string(),
role: z.string().optional(),
roleId: z.string().optional(),
headcount: z.number().int().positive().default(1),
hoursPerDay: z.number().min(0.5).max(24),
startDate: z.coerce.date(),
endDate: z.coerce.date(),
budgetCents: z.number().int().min(0).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
}))
.input(
z.object({
projectId: z.string(),
role: z.string().optional(),
roleId: z.string().optional(),
headcount: z.number().int().positive().default(1),
hoursPerDay: z.number().min(0.5).max(24),
startDate: z.coerce.date(),
endDate: z.coerce.date(),
budgetCents: z.number().int().min(0).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
}),
)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
return createDemandRequirementWithEffects(
ctx.db,
buildCreateDemandRequirementInput(input),
);
return createDemandRequirementWithEffects(ctx.db, buildCreateDemandRequirementInput(input));
}),
updateDemandRequirement: managerProcedure
@@ -110,7 +109,7 @@ export const allocationDemandProcedures = {
entityType: "DemandRequirement",
entityId: input.id,
action: "DELETE",
changes: { before: existing } as unknown as import("@capakraken/db").Prisma.InputJsonValue,
changes: { before: existing } as unknown as Prisma.InputJsonValue,
},
});
});
@@ -127,17 +126,19 @@ export const allocationDemandProcedures = {
}),
fillDemandRequirement: managerProcedure
.input(FillDemandRequirementSchema)
.input(FillDemandRequirementSchema as z.ZodType<FillDemandRequirementInput>)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
return fillDemandRequirementWithEffects(ctx.db, input);
}),
assignResourceToDemand: managerProcedure
.input(z.object({
demandRequirementId: z.string(),
resourceId: z.string(),
}))
.input(
z.object({
demandRequirementId: z.string(),
resourceId: z.string(),
}),
)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
const result = await fillDemandRequirementWithEffects(ctx.db, input);
@@ -153,7 +154,7 @@ export const allocationDemandProcedures = {
}),
fillOpenDemandByAllocation: managerProcedure
.input(FillOpenDemandByAllocationSchema)
.input(FillOpenDemandByAllocationSchema as z.ZodType<FillOpenDemandByAllocationInput>)
.mutation(async ({ ctx, input }) => {
requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);