From fc08a41c4b3d583aa4142d441a4c30698a56b5a3 Mon Sep 17 00:00:00 2001 From: Brandon Li Date: Sun, 30 Aug 2026 16:16:06 -0500 Subject: [PATCH] Fix seven type errors that broke `next build` `npm run build` failed on a clean checkout, so nothing on master could be built for production. `npm run dev` does not hard-fail on type errors, which is why it went unnoticed. - state route returned client.perContractFees, which has never existed on TradovateClient on any branch; nothing consumed it - mapFirmConfig omitted bannedSymbols. Type gap only: the trade path calls isSymbolBanned() against the DB directly, so bans were always enforced - initClient's sync callback was sync where the constructor wants () => Promise - accessInfo and ws are assigned during async connect/auth, never in the constructor, so they take definite-assignment assertions - the socket payload's inline entityType union had drifted five members behind the indirect-callback union above it, making the 'position' and 'cashBalance' branches unreachable to the compiler. Both now share a TradovateEntityType alias. Type-only: those handlers ran fine at runtime Behaviour is unchanged throughout. Co-Authored-By: Claude Opus 5 --- app/api/state/route.ts | 2 +- lib/auto-trade.ts | 3 ++- lib/clients.ts | 2 +- lib/tradovate-class.ts | 38 +++++++++++++++++++++----------------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/api/state/route.ts b/app/api/state/route.ts index 5a28525..68caf08 100644 --- a/app/api/state/route.ts +++ b/app/api/state/route.ts @@ -98,7 +98,7 @@ export async function GET() { fundTransactions: displayFundTxns, }; }); - return { firm: f.name, connected: true, accounts, perContractFees: client.perContractFees }; + return { firm: f.name, connected: true, accounts }; }); return NextResponse.json(state); diff --git a/lib/auto-trade.ts b/lib/auto-trade.ts index 1ab8901..fecbf91 100644 --- a/lib/auto-trade.ts +++ b/lib/auto-trade.ts @@ -7,7 +7,7 @@ * signal but have since exited and are now eligible. */ -import { getFirms, isSymbolBanned, getInstruments } from './db'; +import { getFirms, isSymbolBanned, getInstruments, getBannedSymbols } from './db'; import { getClients } from './clients'; import { computeDailyTarget, resolveEffectiveConfig, POINT_VALUES } from './trading-logic'; import { getSetting } from './db'; @@ -33,6 +33,7 @@ function mapFirmConfig(firm: FirmWithAccounts): FirmConfig { firm: firm.name, username: firm.username, password: firm.password, + bannedSymbols: getBannedSymbols(firm.id), accounts: firm.accounts.map((a) => ({ prefix: a.prefix, profitTarget: a.profit_target, diff --git a/lib/clients.ts b/lib/clients.ts index 10387c1..9ce17ba 100644 --- a/lib/clients.ts +++ b/lib/clients.ts @@ -19,7 +19,7 @@ function ensureMap(): Map { export function initClient(id: number, username: string, password: string, firmName: string): TradovateClient { const map = ensureMap(); - const client = new TradovateClient(username, password, () => { + const client = new TradovateClient(username, password, async () => { console.log(`[${firmName}] sync complete — ${client.accountList.length} account(s)`); }); map.set(id, client); diff --git a/lib/tradovate-class.ts b/lib/tradovate-class.ts index 0694f7c..bd47495 100644 --- a/lib/tradovate-class.ts +++ b/lib/tradovate-class.ts @@ -7,10 +7,27 @@ import { POINT_VALUES } from './trading-logic'; import { getCachedContract, resolveContracts } from './contract-resolver'; import { saveDailyPnL, loadDailyPnL, saveAccountMeta, loadAccountMeta, saveFundTransactions, loadFundTransactions } from './db'; +/** + * Entity types Tradovate sends over the user-sync socket. Keep this as the + * single source of truth: the inline socket-payload type and the indirect + * callback list previously declared it separately and fell out of sync, which + * made the 'position' and 'cashBalance' handlers unreachable to the compiler. + */ +type TradovateEntityType = + | 'order' + | 'orderVersion' + | 'auditUserAction' + | 'command' + | 'commandReport' + | 'fill' + | 'executionReport' + | 'position' + | 'cashBalance'; + export class TradovateClient { private name: string; private password: string; - private accessInfo: AuthLoginResponse; + private accessInfo!: AuthLoginResponse; private deviceId = randomUUIDV4(); public accountList: AccountItem[] = []; @@ -70,7 +87,7 @@ export class TradovateClient { public syncComplete = false; - private ws: WebSocket; + private ws!: WebSocket; private callbackOnSyncRequest: () => Promise; /** Incrementing ID for outgoing WebSocket messages — ensures concurrent orders don't clobber each other's callbacks. */ @@ -82,15 +99,7 @@ export class TradovateClient { [id: number]: (response: any) => void; } = {}; private indirectEventCallbacks: { - entityType: - | 'order' - | 'orderVersion' - | 'auditUserAction' - | 'command' - | 'commandReport' - | 'fill' - | 'executionReport' - | 'cashBalance'; + entityType: TradovateEntityType; eventType: 'Created' | 'Updated'; // Since the entity is not always the same, we need a validator to check if the response is the one we are looking for validator: (response: any) => boolean; @@ -167,12 +176,7 @@ export class TradovateClient { | { e?: string; d?: { - entityType: - | 'order' - | 'orderVersion' - | 'auditUserAction' - | 'command' - | 'commandReport'; + entityType: TradovateEntityType; eventType: 'Created' | 'Updated'; entity: any; };