perf(api): add explicit Prisma selects on hot read paths
Replaces full model includes with field-scoped selects on the resource list (listStaff) query. Avoids fetching large JSONB columns (availability, valueScoreBreakdown) and unused scalar fields (aiSummary, portfolioUrl, fte, resourceType, postalCode, etc.) when only identity/rate fields are needed. Adds RESOURCE_LIST_SELECT constant to packages/api/src/db/selects.ts covering all fields actually consumed by ResourcesClient, FillOpenDemandModal, EstimateWizard, EstimateWorkspaceDraftEditor, and ScenarioPlanner. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,34 @@
|
|||||||
export const ROLE_BRIEF_SELECT = { id: true, name: true, color: true } as const;
|
export const ROLE_BRIEF_SELECT = { id: true, name: true, color: true } as const;
|
||||||
export const PROJECT_BRIEF_SELECT = { id: true, name: true, shortCode: true, status: true, endDate: true } as const;
|
export const PROJECT_BRIEF_SELECT = { id: true, name: true, shortCode: true, status: true, endDate: true } as const;
|
||||||
export const RESOURCE_BRIEF_SELECT = { id: true, displayName: true, eid: true, lcrCents: true, chapter: true } as const;
|
export const RESOURCE_BRIEF_SELECT = { id: true, displayName: true, eid: true, lcrCents: true, chapter: true } as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicit select for the resource list endpoint (listStaff).
|
||||||
|
* Omits large JSONB columns that are unused in all list consumers:
|
||||||
|
* - availability (~several KB per row, only needed for scheduling calculations)
|
||||||
|
* - valueScoreBreakdown (JSON, only needed in detail/dashboard views)
|
||||||
|
* - aiSummary / aiSummaryUpdatedAt / skillMatrixUpdatedAt / portfolioUrl
|
||||||
|
* This keeps skills and dynamicFields because they are rendered in the resource table.
|
||||||
|
*/
|
||||||
|
export const RESOURCE_LIST_SELECT = {
|
||||||
|
id: true,
|
||||||
|
eid: true,
|
||||||
|
displayName: true,
|
||||||
|
email: true,
|
||||||
|
chapter: true,
|
||||||
|
lcrCents: true,
|
||||||
|
ucrCents: true,
|
||||||
|
currency: true,
|
||||||
|
chargeabilityTarget: true,
|
||||||
|
skills: true,
|
||||||
|
dynamicFields: true,
|
||||||
|
blueprintId: true,
|
||||||
|
isActive: true,
|
||||||
|
roleId: true,
|
||||||
|
federalState: true,
|
||||||
|
valueScore: true,
|
||||||
|
rolledOff: true,
|
||||||
|
departed: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
} as const;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FieldType, ResourceType } from "@capakraken/shared";
|
import { FieldType, ResourceType } from "@capakraken/shared";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ROLE_BRIEF_SELECT } from "../db/selects.js";
|
import { RESOURCE_LIST_SELECT, ROLE_BRIEF_SELECT } from "../db/selects.js";
|
||||||
import { buildDynamicFieldWhereClauses } from "./custom-field-filters.js";
|
import { buildDynamicFieldWhereClauses } from "./custom-field-filters.js";
|
||||||
import {
|
import {
|
||||||
anonymizeResources,
|
anonymizeResources,
|
||||||
@@ -167,7 +167,8 @@ export async function listStaffResources(
|
|||||||
const rawResources = await (includeRoles
|
const rawResources = await (includeRoles
|
||||||
? ctx.db.resource.findMany({
|
? ctx.db.resource.findMany({
|
||||||
where,
|
where,
|
||||||
include: {
|
select: {
|
||||||
|
...RESOURCE_LIST_SELECT,
|
||||||
resourceRoles: {
|
resourceRoles: {
|
||||||
include: { role: { select: ROLE_BRIEF_SELECT } },
|
include: { role: { select: ROLE_BRIEF_SELECT } },
|
||||||
},
|
},
|
||||||
@@ -176,6 +177,7 @@ export async function listStaffResources(
|
|||||||
})
|
})
|
||||||
: ctx.db.resource.findMany({
|
: ctx.db.resource.findMany({
|
||||||
where,
|
where,
|
||||||
|
select: RESOURCE_LIST_SELECT,
|
||||||
orderBy: [{ displayName: "asc" }, { id: "asc" }],
|
orderBy: [{ displayName: "asc" }, { id: "asc" }],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -264,13 +266,17 @@ export async function listStaffResources(
|
|||||||
includeRoles
|
includeRoles
|
||||||
? ctx.db.resource.findMany({
|
? ctx.db.resource.findMany({
|
||||||
...baseQuery,
|
...baseQuery,
|
||||||
include: {
|
select: {
|
||||||
|
...RESOURCE_LIST_SELECT,
|
||||||
resourceRoles: {
|
resourceRoles: {
|
||||||
include: { role: { select: ROLE_BRIEF_SELECT } },
|
include: { role: { select: ROLE_BRIEF_SELECT } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
: ctx.db.resource.findMany(baseQuery),
|
: ctx.db.resource.findMany({
|
||||||
|
...baseQuery,
|
||||||
|
select: RESOURCE_LIST_SELECT,
|
||||||
|
}),
|
||||||
ctx.db.resource.count({ where }),
|
ctx.db.resource.count({ where }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user