152 lines
5.0 KiB
TypeScript
152 lines
5.0 KiB
TypeScript
import { getContainer } from '@bytelyst/cosmos';
|
|
import { config } from '../config/index.js';
|
|
import logger from '../utils/logger.js';
|
|
import { getLegacySupabaseClient } from './legacySupabaseClient.js';
|
|
import type { CapitalLedgerRecord } from './CapitalLedger.js';
|
|
|
|
const CONTAINER_NAME = 'capital_ledgers';
|
|
|
|
interface CapitalLedgerDocument {
|
|
id: string;
|
|
productId: string;
|
|
profile_id: string;
|
|
allocated_capital: number;
|
|
reserved_for_orders: number;
|
|
reserved_for_positions: number;
|
|
realized_pnl: number;
|
|
updated_at: string;
|
|
}
|
|
|
|
const toNumeric = (value: unknown): number => {
|
|
const numeric = Number(value);
|
|
return Number.isFinite(numeric) ? numeric : 0;
|
|
};
|
|
|
|
function isCosmosConfigured(): boolean {
|
|
return Boolean(config.COSMOS_ENDPOINT && config.COSMOS_KEY);
|
|
}
|
|
|
|
function getLegacyClient() {
|
|
return getLegacySupabaseClient();
|
|
}
|
|
|
|
function toLedgerRecord(doc: Partial<CapitalLedgerDocument> | null | undefined): CapitalLedgerRecord | null {
|
|
const profileId = String(doc?.profile_id || '').trim();
|
|
if (!profileId) return null;
|
|
|
|
return {
|
|
profile_id: profileId,
|
|
allocated_capital: toNumeric(doc?.allocated_capital),
|
|
reserved_for_orders: toNumeric(doc?.reserved_for_orders),
|
|
reserved_for_positions: toNumeric(doc?.reserved_for_positions),
|
|
realized_pnl: toNumeric(doc?.realized_pnl),
|
|
updated_at: String(doc?.updated_at || new Date().toISOString())
|
|
};
|
|
}
|
|
|
|
function toLedgerDocument(record: CapitalLedgerRecord): CapitalLedgerDocument {
|
|
return {
|
|
id: record.profile_id,
|
|
productId: config.PRODUCT_ID,
|
|
...record
|
|
};
|
|
}
|
|
|
|
async function readFromCosmos(profileId: string): Promise<CapitalLedgerRecord | null> {
|
|
const container = getContainer(CONTAINER_NAME);
|
|
try {
|
|
const { resource } = await container.item(profileId, config.PRODUCT_ID).read<CapitalLedgerDocument>();
|
|
return toLedgerRecord(resource);
|
|
} catch (error) {
|
|
const code = (error as { code?: number })?.code;
|
|
if (code === 404) return null;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function writeToCosmos(record: CapitalLedgerRecord): Promise<CapitalLedgerRecord | null> {
|
|
const container = getContainer(CONTAINER_NAME);
|
|
const { resource } = await container.items.upsert(toLedgerDocument(record));
|
|
return toLedgerRecord(resource as unknown as CapitalLedgerDocument);
|
|
}
|
|
|
|
async function readFromLegacy(profileId: string): Promise<CapitalLedgerRecord | null> {
|
|
const client = getLegacyClient();
|
|
if (!client) return null;
|
|
|
|
const { data, error } = await client
|
|
.from('capital_ledgers')
|
|
.select('*')
|
|
.eq('profile_id', profileId)
|
|
.maybeSingle();
|
|
|
|
if (error) {
|
|
logger.error(`[CapitalLedgerRepo] Legacy read failed for ${profileId}: ${error.message}`);
|
|
return null;
|
|
}
|
|
|
|
return toLedgerRecord(data as CapitalLedgerDocument);
|
|
}
|
|
|
|
export async function getCapitalLedger(profileId: string): Promise<CapitalLedgerRecord | null> {
|
|
if (!profileId) return null;
|
|
|
|
if (!isCosmosConfigured()) {
|
|
return readFromLegacy(profileId);
|
|
}
|
|
|
|
if (isCosmosConfigured()) {
|
|
try {
|
|
const cosmosRecord = await readFromCosmos(profileId);
|
|
if (cosmosRecord) return cosmosRecord;
|
|
const legacyRecord = await readFromLegacy(profileId);
|
|
if (!legacyRecord) return null;
|
|
await writeToCosmos(legacyRecord);
|
|
logger.info(`[CapitalLedgerRepo] Seeded capital ledger ${profileId} from legacy store into Cosmos.`);
|
|
return legacyRecord;
|
|
} catch (error) {
|
|
logger.warn(`[CapitalLedgerRepo] Cosmos read/seed failed for ${profileId}: ${error instanceof Error ? error.message : 'unknown error'}`);
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export async function upsertCapitalLedger(
|
|
record: CapitalLedgerRecord
|
|
): Promise<CapitalLedgerRecord | null> {
|
|
if (!record.profile_id) return null;
|
|
|
|
if (isCosmosConfigured()) {
|
|
try {
|
|
const saved = await writeToCosmos(record);
|
|
if (saved) return saved;
|
|
} catch (error) {
|
|
logger.warn(`[CapitalLedgerRepo] Cosmos upsert failed, falling back to legacy store: ${error instanceof Error ? error.message : 'unknown error'}`);
|
|
}
|
|
}
|
|
|
|
const client = getLegacyClient();
|
|
if (!client) return null;
|
|
|
|
const { data, error } = await client
|
|
.from('capital_ledgers')
|
|
.upsert({
|
|
profile_id: record.profile_id,
|
|
allocated_capital: record.allocated_capital,
|
|
reserved_for_orders: record.reserved_for_orders,
|
|
reserved_for_positions: record.reserved_for_positions,
|
|
realized_pnl: record.realized_pnl,
|
|
updated_at: record.updated_at
|
|
}, { onConflict: 'profile_id' })
|
|
.select('*')
|
|
.maybeSingle();
|
|
|
|
if (error) {
|
|
logger.error(`[CapitalLedgerRepo] Legacy upsert failed for ${record.profile_id}: ${error.message}`);
|
|
return null;
|
|
}
|
|
|
|
return toLedgerRecord(data as CapitalLedgerDocument);
|
|
}
|