import { edenTreaty } from '@elysiajs/eden'; import type { App } from '@/lib/server/api/app'; import type { CompanyAiReportDetail, CompanyAnalysis, Filing, Holding, PortfolioInsight, PortfolioSummary, Task, User, WatchlistItem } from './types'; import { resolveApiBaseURL } from './runtime-url'; const API_BASE = resolveApiBaseURL(process.env.NEXT_PUBLIC_API_URL); const client = edenTreaty(API_BASE, { $fetch: { credentials: 'include', cache: 'no-store' } }); export class ApiError extends Error { status: number; constructor(message: string, status: number) { super(message); this.name = 'ApiError'; this.status = status; } } function extractErrorMessage(error: unknown, fallback: string) { if (!error || typeof error !== 'object') { return fallback; } const candidate = error as { value?: unknown; message?: string; }; if (typeof candidate.message === 'string' && candidate.message.trim().length > 0) { return candidate.message; } if (candidate.value && typeof candidate.value === 'object') { const nested = candidate.value as { error?: unknown; message?: unknown }; if (typeof nested.error === 'string' && nested.error.trim().length > 0) { return nested.error; } if (typeof nested.message === 'string' && nested.message.trim().length > 0) { return nested.message; } } if (typeof candidate.value === 'string' && candidate.value.trim().length > 0) { return candidate.value; } return fallback; } type TreatyResult = { data: unknown; error: unknown; status: number; }; async function unwrapData(result: TreatyResult, fallback: string) { if (result.error) { throw new ApiError( extractErrorMessage(result.error, fallback), result.status ); } if (result.data === null || result.data === undefined) { throw new ApiError(fallback, result.status); } const payload = result.data instanceof Response ? await result.data.json().catch(() => null) : result.data; if (payload === null || payload === undefined) { throw new ApiError(fallback, result.status); } return payload as T; } export async function getMe() { const result = await client.api.me.get(); return await unwrapData<{ user: User }>(result, 'Unable to fetch session'); } export async function listWatchlist() { const result = await client.api.watchlist.get(); return await unwrapData<{ items: WatchlistItem[] }>(result, 'Unable to fetch watchlist'); } export async function upsertWatchlistItem(input: { ticker: string; companyName: string; sector?: string }) { const result = await client.api.watchlist.post(input); return await unwrapData<{ item: WatchlistItem }>(result, 'Unable to save watchlist item'); } export async function deleteWatchlistItem(id: number) { const result = await client.api.watchlist[id].delete(); return await unwrapData<{ success: boolean }>(result, 'Unable to delete watchlist item'); } export async function listHoldings() { const result = await client.api.portfolio.holdings.get(); return await unwrapData<{ holdings: Holding[] }>(result, 'Unable to fetch holdings'); } export async function getPortfolioSummary() { const result = await client.api.portfolio.summary.get(); return await unwrapData<{ summary: PortfolioSummary }>(result, 'Unable to fetch summary'); } export async function upsertHolding(input: { ticker: string; shares: number; avgCost: number; currentPrice?: number; }) { const result = await client.api.portfolio.holdings.post(input); return await unwrapData<{ holding: Holding }>(result, 'Unable to save holding'); } export async function deleteHolding(id: number) { const result = await client.api.portfolio.holdings[id].delete(); return await unwrapData<{ success: boolean }>(result, 'Unable to delete holding'); } export async function queuePriceRefresh() { const result = await client.api.portfolio['refresh-prices'].post(); return await unwrapData<{ task: Task }>(result, 'Unable to queue price refresh'); } export async function queuePortfolioInsights() { const result = await client.api.portfolio.insights.generate.post(); return await unwrapData<{ task: Task }>(result, 'Unable to queue portfolio insights'); } export async function getLatestPortfolioInsight() { const result = await client.api.portfolio.insights.latest.get(); return await unwrapData<{ insight: PortfolioInsight | null }>(result, 'Unable to fetch latest insight'); } export async function listFilings(query?: { ticker?: string; limit?: number }) { const queryParams: { ticker?: string; limit?: number; } = {}; if (query?.ticker?.trim()) { queryParams.ticker = query.ticker.trim().toUpperCase(); } if (query?.limit !== undefined) { queryParams.limit = query.limit; } const result = await client.api.filings.get({ $query: queryParams }); return await unwrapData<{ filings: Filing[] }>(result, 'Unable to fetch filings'); } export async function getCompanyAnalysis(ticker: string) { const result = await client.api.analysis.company.get({ $query: { ticker: ticker.trim().toUpperCase() } }); return await unwrapData<{ analysis: CompanyAnalysis }>(result, 'Unable to fetch company analysis'); } export async function getCompanyAiReport(accessionNumber: string) { const normalizedAccession = accessionNumber.trim(); const result = await client.api.analysis.reports[normalizedAccession].get(); return await unwrapData<{ report: CompanyAiReportDetail }>(result, 'Unable to fetch AI summary'); } export async function queueFilingSync(input: { ticker: string; limit?: number }) { const result = await client.api.filings.sync.post(input); return await unwrapData<{ task: Task }>(result, 'Unable to queue filing sync'); } export async function queueFilingAnalysis(accessionNumber: string) { const result = await client.api.filings[accessionNumber].analyze.post(); return await unwrapData<{ task: Task }>(result, 'Unable to queue filing analysis'); } export async function getTask(taskId: string) { const result = await client.api.tasks[taskId].get(); return await unwrapData<{ task: Task }>(result, 'Unable to fetch task'); } export async function listRecentTasks(limit = 20) { const result = await client.api.tasks.get({ $query: { limit } }); return await unwrapData<{ tasks: Task[] }>(result, 'Unable to fetch tasks'); }