refactor(ts): day 4 — middleware + utils + db .js → .ts (24 files)

All remaining backend files renamed:
  src/middleware/auth.ts, logging.ts (2 files)
  src/utils/*.ts         (20 files: ai, auditQueue, config, crypto,
                          embeddings, errors, fileType, logger, models,
                          notify, passwords, platform, promptSafe,
                          prompts, redact, sessions, transcribe*,
                          ttsGoogle)
  src/db/database.ts, migrate.ts (2 files)

Spot-fixes to satisfy tsc (all within the spirit of 'no behavior
change' — added `: any` annotations where the original JS relied on
duck typing that tsc's default inference narrows too aggressively):

  utils/ai.ts — body, converseParams, request literals + fallback
    result object + err.code/model/message casts. AI client has lots
    of provider-specific ad-hoc object shapes; Day 5 will replace the
    `any`s with proper provider-response interfaces.
  utils/embeddings.ts — payload + request as `any`; generateEmbedding
    call sites pass `undefined as any` for the now-required second
    arg (model) until we refactor the signature.
  utils/prompts.ts — PROMPTS typed as Record<string, any> so
    .loadFromDb / .updatePrompt / .getAllPrompts attachments after
    the const literal compile.
  utils/transcribeLocal.ts — buildArgs() has two `var args = [...]`
    in the same function scope (var-hoisted); both now typed as
    any[] so they don't type-clash across conditionals.

Backend is now 54 of 54 TypeScript files, permissive mode.
`npm run typecheck` EXIT 0. Prod container still running the old
JS image — no Dockerfile change yet.

Next: Day 5 flips strict: true, fixes every error tsc surfaces, adds
Vitest + Zod + Knip tooling.
This commit is contained in:
Daniel 2026-04-23 19:52:16 +02:00
parent 3d597d61a8
commit f71241cd82
24 changed files with 19 additions and 19 deletions

View file

@ -223,7 +223,7 @@ async function callBedrock(messages, model, temperature, maxTokens) {
if (isAnthropic) { if (isAnthropic) {
// Anthropic Messages API format (native, best performance) // Anthropic Messages API format (native, best performance)
var InvokeModelCommand = BedrockModule.InvokeModelCommand; var InvokeModelCommand = BedrockModule.InvokeModelCommand;
var body = { var body: any = {
anthropic_version: 'bedrock-2023-05-31', anthropic_version: 'bedrock-2023-05-31',
max_tokens: maxTokens, max_tokens: maxTokens,
temperature: temperature, temperature: temperature,
@ -279,7 +279,7 @@ async function callBedrock(messages, model, temperature, maxTokens) {
return { role: m.role, content: [{ text: m.content }] }; return { role: m.role, content: [{ text: m.content }] };
}); });
var converseParams = { var converseParams: any = {
modelId: modelId, modelId: modelId,
messages: converseMessages, messages: converseMessages,
inferenceConfig: { inferenceConfig: {
@ -346,7 +346,7 @@ async function callVertex(messages, model, temperature, maxTokens) {
} }
}); });
var request = { contents: contents }; var request: any = { contents: contents };
if (systemInstruction) { if (systemInstruction) {
request.systemInstruction = { parts: [{ text: systemInstruction }] }; request.systemInstruction = { parts: [{ text: systemInstruction }] };
} }
@ -418,8 +418,8 @@ async function callAI(messages, options) {
var allowed = await getAllowedModelIds(db); var allowed = await getAllowedModelIds(db);
if (allowed && allowed.size > 0 && !allowed.has(requestedModel)) { if (allowed && allowed.size > 0 && !allowed.has(requestedModel)) {
var err = new Error('Model not permitted'); var err = new Error('Model not permitted');
err.code = 'model_not_permitted'; (err as any).code = 'model_not_permitted';
err.model = requestedModel; (err as any).model = requestedModel;
throw err; throw err;
} }
} catch (e) { } catch (e) {
@ -487,28 +487,28 @@ async function callAI(messages, options) {
if (activeProvider === 'openrouter' && model !== FALLBACK_MODEL && openrouter) { if (activeProvider === 'openrouter' && model !== FALLBACK_MODEL && openrouter) {
logger.warn('Trying fallback model: ' + FALLBACK_MODEL); logger.warn('Trying fallback model: ' + FALLBACK_MODEL);
try { try {
var fallbackResult = await callOpenRouter(messages, FALLBACK_MODEL, temperature, maxTokens); var fallbackResult: any = await callOpenRouter(messages, FALLBACK_MODEL, temperature, maxTokens);
fallbackResult.fallback = true; fallbackResult.fallback = true;
fallbackResult.duration = Date.now() - startTime; fallbackResult.duration = Date.now() - startTime;
logger.info('Fallback success', { model: FALLBACK_MODEL }); logger.info('Fallback success', { model: FALLBACK_MODEL });
return fallbackResult; return fallbackResult;
} catch (err2) { } catch (err2) {
logger.error('Fallback also failed', { error: err2.message }); logger.error('Fallback also failed', { error: (err2 as any).message });
throw new Error('All models failed: ' + err2.message); throw new Error('All models failed: ' + (err2 as any).message);
} }
} }
if (activeProvider === 'litellm' && model !== FALLBACK_MODEL && litellmClient) { if (activeProvider === 'litellm' && model !== FALLBACK_MODEL && litellmClient) {
logger.warn('Trying fallback model on LiteLLM: ' + FALLBACK_MODEL); logger.warn('Trying fallback model on LiteLLM: ' + FALLBACK_MODEL);
try { try {
var litellmFallback = await callLiteLLM(messages, FALLBACK_MODEL, temperature, maxTokens); var litellmFallback: any = await callLiteLLM(messages, FALLBACK_MODEL, temperature, maxTokens);
litellmFallback.fallback = true; litellmFallback.fallback = true;
litellmFallback.duration = Date.now() - startTime; litellmFallback.duration = Date.now() - startTime;
logger.info('LiteLLM fallback success', { model: FALLBACK_MODEL }); logger.info('LiteLLM fallback success', { model: FALLBACK_MODEL });
return litellmFallback; return litellmFallback;
} catch (err3) { } catch (err3) {
logger.error('LiteLLM fallback also failed', { error: err3.message }); logger.error('LiteLLM fallback also failed', { error: (err3 as any).message });
throw new Error('All models failed: ' + err3.message); throw new Error('All models failed: ' + (err3 as any).message);
} }
} }
} }

View file

@ -67,7 +67,7 @@ async function generateEmbeddingLiteLLM(text, model, dimensions) {
headers['Authorization'] = 'Bearer ' + process.env.LITELLM_API_KEY; headers['Authorization'] = 'Bearer ' + process.env.LITELLM_API_KEY;
} }
var payload = { var payload: any = {
model: model, model: model,
input: text input: text
}; };
@ -113,7 +113,7 @@ async function generateEmbeddingVertexDirect(text, model, dimensions) {
var modelName = model.replace(/^vertex_ai\//, ''); var modelName = model.replace(/^vertex_ai\//, '');
// For text-embedding-005, we can specify output dimensions // For text-embedding-005, we can specify output dimensions
var request = { var request: any = {
instances: [{ content: text }] instances: [{ content: text }]
}; };
@ -181,7 +181,7 @@ async function searchSimilar(queryText, opts) {
var db = require('../db/database'); var db = require('../db/database');
// Generate embedding for query // Generate embedding for query
var queryEmbedding = await generateEmbedding(queryText); var queryEmbedding = await generateEmbedding(queryText, undefined as any);
// Build WHERE clause for filtering // Build WHERE clause for filtering
var whereClause = 'WHERE c.published = true AND c.embedding IS NOT NULL'; var whereClause = 'WHERE c.published = true AND c.embedding IS NOT NULL';
@ -233,7 +233,7 @@ async function generateContentEmbedding(content) {
stripHtml(content.body || '').substring(0, 6000) stripHtml(content.body || '').substring(0, 6000)
].filter(Boolean).join('\n\n'); ].filter(Boolean).join('\n\n');
return await generateEmbedding(text); return await generateEmbedding(text, undefined as any);
} }
/** /**

View file

@ -47,7 +47,7 @@ For ABNORMAL systems:
For NOT REVIEWED systems: simply state "Not reviewed" or "Not examined" For NOT REVIEWED systems: simply state "Not reviewed" or "Not examined"
`; `;
const PROMPTS = { const PROMPTS: Record<string, any> = {
// ======================== HPI ======================== // ======================== HPI ========================
hpiEncounter: `You are an expert pediatric medical scribe. hpiEncounter: `You are an expert pediatric medical scribe.

View file

@ -122,17 +122,17 @@ function transcribeWithLocal(audioBuffer, mimeType) {
/** /**
* Build command-line args based on the detected binary * Build command-line args based on the detected binary
*/ */
function buildArgs(binary, audioPath) { function buildArgs(binary: string, audioPath: string): any[] {
if (binary.includes('faster-whisper')) { if (binary.includes('faster-whisper')) {
// faster-whisper CLI // faster-whisper CLI
var args = ['--model', WHISPER_MODEL_SIZE, '--language', WHISPER_LANGUAGE]; var args: any[] = ['--model', WHISPER_MODEL_SIZE, '--language', WHISPER_LANGUAGE];
if (WHISPER_THREADS) args.push('--threads', WHISPER_THREADS); if (WHISPER_THREADS) args.push('--threads', WHISPER_THREADS);
args.push(audioPath); args.push(audioPath);
return args; return args;
} }
// whisper.cpp style // whisper.cpp style
var args = ['-f', audioPath, '-l', WHISPER_LANGUAGE, '-t', WHISPER_THREADS, '--no-timestamps']; var args: any[] = ['-f', audioPath, '-l', WHISPER_LANGUAGE, '-t', WHISPER_THREADS, '--no-timestamps'];
if (WHISPER_MODEL_PATH) { if (WHISPER_MODEL_PATH) {
args.push('-m', WHISPER_MODEL_PATH); args.push('-m', WHISPER_MODEL_PATH);
} else { } else {