refactor: eliminate all catch(err: any) with proper unknown typing + type email transport
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 19m10s

- Replace all catch(err: any) with catch(err: unknown) across 8 source files
- Add errorMessage() and errorCode() helpers for safe error property access
- Type nodemailer transport config as SMTPTransport.Options (was any)
- Type health endpoint databaseStatus (was any)
- Type convert route margin param (was any)
- Change queryWithRetry params from any[] to unknown[]
- Update isTransientError to require Error instances (was accepting plain objects)
- 19 new TDD tests (error-type-safety.test.ts)
- Updated existing tests to use proper Error instances
- 598 tests total, all passing, zero type errors
This commit is contained in:
DocFast CEO 2026-03-09 11:10:58 +01:00
parent da049b77e3
commit 5a7ee79316
12 changed files with 221 additions and 98 deletions

View file

@ -1,7 +1,7 @@
import pg from "pg";
import logger from "./logger.js";
import { isTransientError } from "../utils/errors.js";
import { isTransientError, errorMessage, errorCode } from "../utils/errors.js";
const { Pool } = pg;
const pool = new Pool({
@ -35,10 +35,10 @@ export { isTransientError } from "../utils/errors.js";
*/
export async function queryWithRetry(
queryText: string,
params?: any[],
params?: unknown[],
maxRetries = 3
): Promise<pg.QueryResult> {
let lastError: any;
let lastError: unknown;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
let client: pg.PoolClient | undefined;
@ -47,7 +47,7 @@ export async function queryWithRetry(
const result = await client.query(queryText, params);
client.release(); // Return healthy connection to pool
return result;
} catch (err: any) {
} catch (err: unknown) {
// Destroy the bad connection so pool doesn't reuse it
if (client) {
try { client.release(true); } catch (_) { /* already destroyed */ }
@ -61,7 +61,7 @@ export async function queryWithRetry(
const delayMs = Math.min(1000 * Math.pow(2, attempt), 5000); // 1s, 2s, 4s (capped at 5s)
logger.warn(
{ err: err.message, code: err.code, attempt: attempt + 1, maxRetries, delayMs },
{ err: errorMessage(err), code: errorCode(err), attempt: attempt + 1, maxRetries, delayMs },
"Transient DB error, destroying bad connection and retrying..."
);
await new Promise(resolve => setTimeout(resolve, delayMs));
@ -77,7 +77,7 @@ export async function queryWithRetry(
* fresh connections to the new PgBouncer pod.
*/
export async function connectWithRetry(maxRetries = 3): Promise<pg.PoolClient> {
let lastError: any;
let lastError: unknown;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
@ -85,7 +85,7 @@ export async function connectWithRetry(maxRetries = 3): Promise<pg.PoolClient> {
// Validate the connection is actually alive
try {
await client.query("SELECT 1");
} catch (validationErr: any) {
} catch (validationErr: unknown) {
// Connection is dead — destroy it and retry
try { client.release(true); } catch (_) {}
if (!isTransientError(validationErr) || attempt === maxRetries) {
@ -93,14 +93,14 @@ export async function connectWithRetry(maxRetries = 3): Promise<pg.PoolClient> {
}
const delayMs = Math.min(1000 * Math.pow(2, attempt), 5000);
logger.warn(
{ err: validationErr.message, code: validationErr.code, attempt: attempt + 1 },
{ err: errorMessage(validationErr), code: errorCode(validationErr), attempt: attempt + 1 },
"Connection validation failed, destroying and retrying..."
);
await new Promise(resolve => setTimeout(resolve, delayMs));
continue;
}
return client;
} catch (err: any) {
} catch (err: unknown) {
lastError = err;
if (!isTransientError(err) || attempt === maxRetries) {
@ -109,7 +109,7 @@ export async function connectWithRetry(maxRetries = 3): Promise<pg.PoolClient> {
const delayMs = Math.min(1000 * Math.pow(2, attempt), 5000);
logger.warn(
{ err: err.message, code: err.code, attempt: attempt + 1, maxRetries, delayMs },
{ err: errorMessage(err), code: errorCode(err), attempt: attempt + 1, maxRetries, delayMs },
"Transient DB connect error, retrying..."
);
await new Promise(resolve => setTimeout(resolve, delayMs));