43 lines
1.5 KiB
JavaScript
43 lines
1.5 KiB
JavaScript
import { lineOf } from '../util.mjs';
|
|
|
|
export const metadata = {
|
|
id: 'prisma-include-tree-bloat',
|
|
title: 'Deep Prisma include tree (3+ levels)',
|
|
severity: 'high',
|
|
billingDimension: 'function-duration',
|
|
trafficIndependent: false,
|
|
description:
|
|
'Nested .include({ x: { include: { y: { include: { z: ... } } } } }) makes Prisma issue a single huge join that scales O(N*M*K). Function duration explodes, memory spikes, often causes timeouts.',
|
|
fix:
|
|
'Replace with explicit .findMany() calls or scoped .include() of only what the consumer reads. Consider Prisma.select() to project specific fields. For lists, batch with DataLoader patterns.',
|
|
citations: [
|
|
'vercel-react-best-practices:server-parallel-fetching',
|
|
],
|
|
excludeGlobs: ['node_modules/**', '.next/**', 'dist/**', '__tests__/**'],
|
|
includeGlobs: ['**/*.{ts,tsx,js,jsx}'],
|
|
};
|
|
|
|
// Catches 3+ nesting levels of `include:` within a single object literal.
|
|
const RE = /include\s*:\s*\{[\s\S]*?include\s*:\s*\{[\s\S]*?include\s*:/g;
|
|
|
|
export function scan({ files }) {
|
|
const out = [];
|
|
for (const { path, content } of files) {
|
|
if (/\.test\.|\.spec\./.test(path)) continue;
|
|
let m;
|
|
RE.lastIndex = 0;
|
|
while ((m = RE.exec(content)) !== null) {
|
|
out.push({
|
|
pattern: metadata.id,
|
|
file: path,
|
|
line: lineOf(content, m.index),
|
|
evidence: '3+ levels of nested Prisma .include()',
|
|
trafficIndependent: metadata.trafficIndependent,
|
|
});
|
|
// One finding per file is enough — agent investigates holistically.
|
|
break;
|
|
}
|
|
}
|
|
return out;
|
|
}
|