Skip to content

Commit 16dda1a

Browse files
refactor: database scripts and models for improved performance and error handling
1 parent b4a98d0 commit 16dda1a

13 files changed

Lines changed: 431 additions & 347 deletions

File tree

scripts/fix-max-files.mjs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,12 @@ const sql = postgres(databaseUrl, { onnotice: () => {} });
3030

3131
async function run() {
3232
try {
33-
const rows = await sql`SELECT repository_id, parsed_json FROM repo_configs`;
34-
let count = 0;
35-
for (const row of rows) {
36-
let parsed = row.parsed_json;
37-
if (typeof parsed === 'string') {
38-
parsed = JSON.parse(parsed);
39-
}
40-
if (parsed?.review?.max_files === 15) {
41-
parsed.review.max_files = 100;
42-
await sql`
43-
UPDATE repo_configs
44-
SET parsed_json = ${JSON.stringify(parsed)}::jsonb
45-
WHERE repository_id = ${row.repository_id}
46-
`;
47-
count++;
48-
}
49-
}
50-
console.log(`Updated ${count} repository configurations from 15 to 100.`);
33+
const result = await sql`
34+
UPDATE repo_configs
35+
SET parsed_json = jsonb_set(parsed_json, '{review,max_files}', '100'::jsonb)
36+
WHERE parsed_json#>>'{review,max_files}' = '15'
37+
`;
38+
console.log(`Updated ${result.count} repository configurations from 15 to 100.`);
5139
} catch (err) {
5240
console.error(err);
5341
} finally {

scripts/migrate.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ async function ensureModelCatalog() {
309309
FROM llm_providers provider_record
310310
WHERE mc.provider_id IS NULL
311311
AND provider_record.name = 'Cloudflare'
312+
AND mc.model_id LIKE '@cf/%'
312313
`,
313314
);
314315

@@ -487,6 +488,7 @@ async function main() {
487488

488489
console.log('Running catalog and config normalizations...');
489490
await query('DROP INDEX IF EXISTS repositories_owner_idx');
491+
await query('CREATE INDEX IF NOT EXISTS repositories_owner_idx ON repositories (owner)');
490492
await ensureModelCatalog();
491493
await normalizeRepoConfigs();
492494

scripts/setup-cloudflare.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -443,23 +443,25 @@ async function main() {
443443
let wranglerConfig = fs.readFileSync(WRANGLER_JSONC_PATH, 'utf-8');
444444
let configChanged = false;
445445

446+
const escapeJson = (str) => str.replace(/"/g, '\\"');
447+
446448
const routeRegex = /"routes"\s*:\s*\[[\s\S]*?\]|"workers_dev"\s*:\s*(true|false)/;
447449
wranglerConfig = wranglerConfig.replace(routeRegex, routesConfigStr);
448450

449451
const appUrlRegex = /"APP_URL":\s*"[^"]+"/;
450-
wranglerConfig = wranglerConfig.replace(appUrlRegex, `"APP_URL": "${appUrl}"`);
452+
wranglerConfig = wranglerConfig.replace(appUrlRegex, `"APP_URL": "${escapeJson(appUrl)}"`);
451453

452454
const callbackUrlRegex = /"AUTH_CALLBACK_URL":\s*"[^"]+"/;
453-
wranglerConfig = wranglerConfig.replace(callbackUrlRegex, `"AUTH_CALLBACK_URL": "${appUrl}/auth/github/callback"`);
455+
wranglerConfig = wranglerConfig.replace(callbackUrlRegex, `"AUTH_CALLBACK_URL": "${escapeJson(appUrl)}/auth/github/callback"`);
454456

455457
const botUsernameRegex = /"BOT_USERNAME":\s*"[^"]+"/;
456-
wranglerConfig = wranglerConfig.replace(botUsernameRegex, `"BOT_USERNAME": "${botUsername}"`);
458+
wranglerConfig = wranglerConfig.replace(botUsernameRegex, `"BOT_USERNAME": "${escapeJson(botUsername)}"`);
457459

458460
const githubAppSlugRegex = /"GITHUB_APP_SLUG":\s*"[^"]+"/;
459-
wranglerConfig = wranglerConfig.replace(githubAppSlugRegex, `"GITHUB_APP_SLUG": "${githubAppSlug}"`);
461+
wranglerConfig = wranglerConfig.replace(githubAppSlugRegex, `"GITHUB_APP_SLUG": "${escapeJson(githubAppSlug)}"`);
460462

461463
const allowedUsersRegex = /"DASHBOARD_ALLOWED_USERS":\s*"[^"]+"/;
462-
wranglerConfig = wranglerConfig.replace(allowedUsersRegex, `"DASHBOARD_ALLOWED_USERS": "${allowedUsers}"`);
464+
wranglerConfig = wranglerConfig.replace(allowedUsersRegex, `"DASHBOARD_ALLOWED_USERS": "${escapeJson(allowedUsers)}"`);
463465

464466
configChanged = true;
465467

src/server/core/github.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ async function withRetry<T>(
2727
attempt++;
2828
const isSecondaryRateLimit = error instanceof GitHubError &&
2929
error.status === 403 &&
30-
error.body.toLowerCase().includes('secondary rate limit');
30+
error.body?.toLowerCase().includes('secondary rate limit');
3131

3232
const isRetryable =
3333
isSecondaryRateLimit ||

src/server/core/model-output.ts

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ function extractJson(raw: string) {
137137
// Truncated JSON: the closing brace(s) are missing. Append them so jsonrepair
138138
// has a structurally complete (though incomplete-content) object to work with.
139139
const partial = raw.slice(firstBrace).trim();
140-
const closing = '}'.repeat(Math.max(1, stack));
140+
let closing = '';
141+
if (inString) closing += '"';
142+
closing += '}'.repeat(Math.max(1, stack));
141143
return `${partial}${closing}`;
142144
}
143145

@@ -157,36 +159,37 @@ function coerceReviewNumber(value: unknown) {
157159
return undefined;
158160
}
159161

160-
function normalizeFinding(finding: any) {
162+
function normalizeFinding(finding: unknown) {
161163
if (!finding || typeof finding !== 'object') return null;
162-
if (isPlaceholderString(finding.title) || isPlaceholderString(finding.body)) return null;
164+
const f = finding as Record<string, unknown>;
165+
if (isPlaceholderString(f.title) || isPlaceholderString(f.body)) return null;
163166

164-
const location = finding.code_location && typeof finding.code_location === 'object' ? finding.code_location : {};
167+
const location = f.code_location && typeof f.code_location === 'object' ? (f.code_location as Record<string, unknown>) : {};
165168
const line = coerceReviewNumber(location.line);
166-
const start = coerceReviewNumber(location.line_range?.start);
167-
const end = coerceReviewNumber(location.line_range?.end);
168-
const priority = coerceReviewNumber(finding.priority);
169+
const start = coerceReviewNumber(location.line_range && typeof location.line_range === 'object' ? (location.line_range as Record<string, unknown>).start : undefined);
170+
const end = coerceReviewNumber(location.line_range && typeof location.line_range === 'object' ? (location.line_range as Record<string, unknown>).end : undefined);
171+
const priority = coerceReviewNumber(f.priority);
169172

170173
const codeLocation: Record<string, unknown> = {
171-
absolute_file_path: location.absolute_file_path || finding.path || '',
174+
absolute_file_path: location.absolute_file_path || f.path || '',
172175
};
173176
if (line !== undefined) {
174-
codeLocation.line = Math.trunc(line);
177+
codeLocation.line = Math.trunc(line as number);
175178
}
176179
if (start !== undefined || end !== undefined) {
177180
codeLocation.line_range = {
178-
start: Math.trunc(start ?? end!),
179-
end: Math.trunc(end ?? start!),
181+
start: Math.trunc((start as number) ?? (end as number)!),
182+
end: Math.trunc((end as number) ?? (start as number)!),
180183
};
181184
}
182185

183186
return {
184-
...finding,
185-
title: finding.title || 'Code finding',
186-
priority: priority === undefined ? undefined : Math.max(0, Math.min(3, Math.trunc(priority))),
187+
...f,
188+
title: f.title || 'Code finding',
189+
priority: priority === undefined ? undefined : Math.max(0, Math.min(3, Math.trunc(priority as number))),
187190
code_location: codeLocation,
188-
confidence_score: typeof finding.confidence_score === 'number'
189-
? Math.max(0, Math.min(1, finding.confidence_score > 1 ? finding.confidence_score / 10 : finding.confidence_score))
191+
confidence_score: typeof f.confidence_score === 'number'
192+
? Math.max(0, Math.min(1, f.confidence_score > 1 ? f.confidence_score / 10 : f.confidence_score))
190193
: undefined,
191194
};
192195
}
@@ -288,7 +291,7 @@ export function parseFileReviewResponse(raw: string, file: FileDiff): {
288291
logger.warn('jsonrepair failed to fix model output, using preprocessed text', { preprocessed: truncateJsonForLog(preprocessed), error: e });
289292
}
290293

291-
let parsedJson: any;
294+
let parsedJson: unknown;
292295
try {
293296
parsedJson = JSON.parse(repaired);
294297
} catch (e) {
@@ -298,13 +301,13 @@ export function parseFileReviewResponse(raw: string, file: FileDiff): {
298301

299302
let parsed: z.infer<typeof fileReviewModelOutputSchema>;
300303
try {
301-
const findReviewObject = (arr: any[]): any | null => {
304+
const findReviewObject = (arr: unknown[]): unknown | null => {
302305
// Priority 1: Has findings array and summary
303-
const best = arr.find(i => i && typeof i === 'object' && Array.isArray(i.findings) && typeof i.summary === 'string');
306+
const best = arr.find(i => i && typeof i === 'object' && Array.isArray((i as Record<string, unknown>).findings) && typeof (i as Record<string, unknown>).summary === 'string');
304307
if (best) return best;
305308

306309
// Priority 2: Has findings array
307-
const good = arr.find(i => i && typeof i === 'object' && Array.isArray(i.findings));
310+
const good = arr.find(i => i && typeof i === 'object' && Array.isArray((i as Record<string, unknown>).findings));
308311
if (good) return good;
309312

310313
// Priority 3: Has review-like keys
@@ -314,29 +317,31 @@ export function parseFileReviewResponse(raw: string, file: FileDiff): {
314317
);
315318
};
316319

317-
const data = Array.isArray(parsedJson) ? (findReviewObject(parsedJson) || parsedJson[0] || {}) : parsedJson;
320+
let data = Array.isArray(parsedJson) ? (findReviewObject(parsedJson) || parsedJson[0] || {}) : parsedJson;
318321

319322
// Ensure essential keys exist to avoid schema validation errors
320323
if (data && typeof data === 'object') {
321-
if (!data.findings) data.findings = [];
322-
if (!data.overall_explanation) data.overall_explanation = 'No explanation provided.';
323-
if (!data.overall_correctness) data.overall_correctness = 'Uncertain';
324+
const obj = data as Record<string, unknown>;
325+
if (!obj.findings) obj.findings = [];
326+
if (!obj.overall_explanation) obj.overall_explanation = 'No explanation provided.';
327+
if (!obj.overall_correctness) obj.overall_correctness = 'Uncertain';
324328

325329
// Handle confidence score hallucinations (0-1 range expected)
326-
if (typeof data.overall_confidence_score === 'number') {
327-
if (data.overall_confidence_score > 1) {
330+
if (typeof obj.overall_confidence_score === 'number') {
331+
if (obj.overall_confidence_score > 1) {
328332
// If they gave 1-10 scale, normalize it
329-
data.overall_confidence_score = Math.min(data.overall_confidence_score / 10, 1);
330-
} else if (data.overall_confidence_score < 0) {
331-
data.overall_confidence_score = 0;
333+
obj.overall_confidence_score = Math.min(obj.overall_confidence_score / 10, 1);
334+
} else if (obj.overall_confidence_score < 0) {
335+
obj.overall_confidence_score = 0;
332336
}
333337
} else {
334-
data.overall_confidence_score = 0.5;
338+
obj.overall_confidence_score = 0.5;
335339
}
336340

337-
if (Array.isArray(data.findings)) {
338-
data.findings = data.findings.map(normalizeFinding).filter(Boolean);
341+
if (Array.isArray(obj.findings)) {
342+
obj.findings = obj.findings.map(normalizeFinding).filter(Boolean);
339343
}
344+
data = obj;
340345
}
341346

342347
parsed = fileReviewModelOutputSchema.parse(data);

src/server/db/client.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { AppBindings } from '@server/env';
55
type DbEnv = Pick<AppBindings, 'HYPERDRIVE'>;
66
type DbClient = {
77
query<T>(sqlText: string, params?: unknown[]): Promise<T[]>;
8+
transaction<T>(fn: (tx: DbClient) => Promise<T>): Promise<T>;
89
};
910

1011
const dbStorage = new AsyncLocalStorage<DbClient>();
@@ -21,6 +22,20 @@ function createDbClient(env: DbEnv): DbClient {
2122
async query<T>(sqlText: string, params: unknown[] = []) {
2223
return (await sql.unsafe(sqlText, params.map(normalizeParam) as any[], { prepare: false })) as T[];
2324
},
25+
async transaction<T>(fn: (tx: DbClient) => Promise<T>) {
26+
return (await sql.begin(async (t) => {
27+
const txClient: DbClient = {
28+
async query<U>(sqlText: string, params: unknown[] = []) {
29+
return (await t.unsafe(sqlText, params.map(normalizeParam) as any[], { prepare: false })) as U[];
30+
},
31+
async transaction<U>(innerFn: (tx: DbClient) => Promise<U>) {
32+
// Nested transactions could use savepoints, but for now we just reuse the same txClient
33+
return await innerFn(txClient);
34+
}
35+
};
36+
return await fn(txClient);
37+
})) as T;
38+
}
2439
};
2540
}
2641

@@ -55,6 +70,10 @@ export async function queryRows<T>(env: DbEnv, sqlText: string, params: unknown[
5570
return getDb(env).query<T>(sqlText, params);
5671
}
5772

73+
export async function queryTransaction<T>(env: DbEnv, fn: (tx: DbClient) => Promise<T>) {
74+
return getDb(env).transaction<T>(fn);
75+
}
76+
5877
export function parseJsonColumn<T>(value: T | string | null | undefined, fallback: T): T {
5978
if (value === null || value === undefined) return fallback;
6079
if (typeof value === 'string') return JSON.parse(value) as T;

0 commit comments

Comments
 (0)