release: WRNexusJS 0.8.0
This commit is contained in:
@@ -28,6 +28,24 @@ export interface QueryPolicy {
|
||||
onIssue?: (issue: QueryIssue) => void | Promise<void>;
|
||||
}
|
||||
|
||||
const recentQueries: QueryRecord[] = [];
|
||||
const recentQueryIssues: QueryIssue[] = [];
|
||||
const TELEMETRY_LIMIT = 200;
|
||||
|
||||
export function getDbPerformanceSnapshot(): { queries: QueryRecord[]; issues: QueryIssue[] } {
|
||||
return { queries: structuredClone(recentQueries), issues: structuredClone(recentQueryIssues) };
|
||||
}
|
||||
|
||||
export function resetDbPerformanceSnapshot(): void {
|
||||
recentQueries.length = 0;
|
||||
recentQueryIssues.length = 0;
|
||||
}
|
||||
|
||||
function remember<T>(items: T[], value: T): void {
|
||||
items.push(structuredClone(value));
|
||||
if (items.length > TELEMETRY_LIMIT) items.splice(0, items.length - TELEMETRY_LIMIT);
|
||||
}
|
||||
|
||||
function normalizedSql(sql: string): string {
|
||||
return sql
|
||||
.replace(/--.*$/gm, " ")
|
||||
@@ -70,12 +88,14 @@ export function instrumentDb(db: Db, policy: QueryPolicy = {}): Db {
|
||||
const inspect = async (sql: string): Promise<void> => {
|
||||
const normalized = normalizedSql(sql);
|
||||
if (policy.warnSelectStar !== false && /^SELECT\s+\*/i.test(normalized)) {
|
||||
await policy.onIssue?.({
|
||||
const issue: QueryIssue = {
|
||||
code: "WRN-DB-SELECT-STAR",
|
||||
severity: "warning",
|
||||
message: "Avoid SELECT * in production queries.",
|
||||
sql,
|
||||
});
|
||||
};
|
||||
remember(recentQueryIssues, issue);
|
||||
await policy.onIssue?.(issue);
|
||||
}
|
||||
if (
|
||||
policy.warnUnboundedSelect !== false &&
|
||||
@@ -83,12 +103,14 @@ export function instrumentDb(db: Db, policy: QueryPolicy = {}): Db {
|
||||
!/\bLIMIT\b/i.test(normalized) &&
|
||||
!/\bCOUNT\s*\(/i.test(normalized)
|
||||
) {
|
||||
await policy.onIssue?.({
|
||||
const issue: QueryIssue = {
|
||||
code: "WRN-DB-UNBOUNDED-SELECT",
|
||||
severity: "warning",
|
||||
message: "SELECT query has no LIMIT. Prefer cursor pagination for large datasets.",
|
||||
sql,
|
||||
});
|
||||
};
|
||||
remember(recentQueryIssues, issue);
|
||||
await policy.onIssue?.(issue);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -110,22 +132,27 @@ export function instrumentDb(db: Db, policy: QueryPolicy = {}): Db {
|
||||
operation,
|
||||
duplicateCount,
|
||||
};
|
||||
remember(recentQueries, queryRecord);
|
||||
await policy.onQuery?.(queryRecord);
|
||||
if (durationMs >= slowQueryMs) {
|
||||
await policy.onIssue?.({
|
||||
const issue: QueryIssue = {
|
||||
code: "WRN-DB-SLOW-QUERY",
|
||||
severity: durationMs >= slowQueryMs * 5 ? "error" : "warning",
|
||||
message: `Query took ${durationMs.toFixed(2)} ms.`,
|
||||
sql,
|
||||
});
|
||||
};
|
||||
remember(recentQueryIssues, issue);
|
||||
await policy.onIssue?.(issue);
|
||||
}
|
||||
if (duplicateCount === duplicateWarningCount) {
|
||||
await policy.onIssue?.({
|
||||
const issue: QueryIssue = {
|
||||
code: "WRN-DB-DUPLICATE-QUERY",
|
||||
severity: "warning",
|
||||
message: `The same query ran ${duplicateCount} times in one request scope (possible N+1).`,
|
||||
sql,
|
||||
});
|
||||
};
|
||||
remember(recentQueryIssues, issue);
|
||||
await policy.onIssue?.(issue);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user