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<void>
- 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 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-08-30 16:16:06 -05:00
co-authored by Claude Opus 5
parent 3cc7ddcc5c
commit fc08a41c4b
4 changed files with 25 additions and 20 deletions
+1 -1
View File
@@ -98,7 +98,7 @@ export async function GET() {
fundTransactions: displayFundTxns, fundTransactions: displayFundTxns,
}; };
}); });
return { firm: f.name, connected: true, accounts, perContractFees: client.perContractFees }; return { firm: f.name, connected: true, accounts };
}); });
return NextResponse.json(state); return NextResponse.json(state);
+2 -1
View File
@@ -7,7 +7,7 @@
* signal but have since exited and are now eligible. * 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 { getClients } from './clients';
import { computeDailyTarget, resolveEffectiveConfig, POINT_VALUES } from './trading-logic'; import { computeDailyTarget, resolveEffectiveConfig, POINT_VALUES } from './trading-logic';
import { getSetting } from './db'; import { getSetting } from './db';
@@ -33,6 +33,7 @@ function mapFirmConfig(firm: FirmWithAccounts): FirmConfig {
firm: firm.name, firm: firm.name,
username: firm.username, username: firm.username,
password: firm.password, password: firm.password,
bannedSymbols: getBannedSymbols(firm.id),
accounts: firm.accounts.map((a) => ({ accounts: firm.accounts.map((a) => ({
prefix: a.prefix, prefix: a.prefix,
profitTarget: a.profit_target, profitTarget: a.profit_target,
+1 -1
View File
@@ -19,7 +19,7 @@ function ensureMap(): Map<number, TradovateClient> {
export function initClient(id: number, username: string, password: string, firmName: string): TradovateClient { export function initClient(id: number, username: string, password: string, firmName: string): TradovateClient {
const map = ensureMap(); 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)`); console.log(`[${firmName}] sync complete — ${client.accountList.length} account(s)`);
}); });
map.set(id, client); map.set(id, client);
+21 -17
View File
@@ -7,10 +7,27 @@ import { POINT_VALUES } from './trading-logic';
import { getCachedContract, resolveContracts } from './contract-resolver'; import { getCachedContract, resolveContracts } from './contract-resolver';
import { saveDailyPnL, loadDailyPnL, saveAccountMeta, loadAccountMeta, saveFundTransactions, loadFundTransactions } from './db'; 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 { export class TradovateClient {
private name: string; private name: string;
private password: string; private password: string;
private accessInfo: AuthLoginResponse; private accessInfo!: AuthLoginResponse;
private deviceId = randomUUIDV4(); private deviceId = randomUUIDV4();
public accountList: AccountItem[] = []; public accountList: AccountItem[] = [];
@@ -70,7 +87,7 @@ export class TradovateClient {
public syncComplete = false; public syncComplete = false;
private ws: WebSocket; private ws!: WebSocket;
private callbackOnSyncRequest: () => Promise<void>; private callbackOnSyncRequest: () => Promise<void>;
/** Incrementing ID for outgoing WebSocket messages — ensures concurrent orders don't clobber each other's callbacks. */ /** 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; [id: number]: (response: any) => void;
} = {}; } = {};
private indirectEventCallbacks: { private indirectEventCallbacks: {
entityType: entityType: TradovateEntityType;
| 'order'
| 'orderVersion'
| 'auditUserAction'
| 'command'
| 'commandReport'
| 'fill'
| 'executionReport'
| 'cashBalance';
eventType: 'Created' | 'Updated'; 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 // 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; validator: (response: any) => boolean;
@@ -167,12 +176,7 @@ export class TradovateClient {
| { | {
e?: string; e?: string;
d?: { d?: {
entityType: entityType: TradovateEntityType;
| 'order'
| 'orderVersion'
| 'auditUserAction'
| 'command'
| 'commandReport';
eventType: 'Created' | 'Updated'; eventType: 'Created' | 'Updated';
entity: any; entity: any;
}; };