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,
};
});
return { firm: f.name, connected: true, accounts, perContractFees: client.perContractFees };
return { firm: f.name, connected: true, accounts };
});
return NextResponse.json(state);
+2 -1
View File
@@ -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,
+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 {
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);
+21 -17
View File
@@ -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<void>;
/** 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;
};