Skip to main content
Glama
fetch-transactions.ts3.55 kB
import { getTransactions } from '../../actual-api.js'; import { fetchAllPayees } from './fetch-payees.js'; import { fetchAllCategories } from './fetch-categories.js'; import { GroupAggregator } from '../aggregation/group-by.js'; import type { Account, Transaction, Payee, Category } from '../types/domain.js'; import { TransactionEntity } from '@actual-app/api/@types/loot-core/src/types/models/transaction.js'; const groupAggregator = new GroupAggregator(); interface TransactionLookupOptions { includePayees: boolean; includeCategories: boolean; } interface TransactionLookups { payeesById: Record<string, Payee>; categoriesById: Record<string, Category>; } async function _buildTransactionLookups(options: TransactionLookupOptions): Promise<TransactionLookups> { const [payees, categories] = await Promise.all([ options.includePayees ? fetchAllPayees() : Promise.resolve<Payee[]>([]), options.includeCategories ? fetchAllCategories() : Promise.resolve<Category[]>([]), ]); const payeesById: Record<string, Payee> = options.includePayees ? groupAggregator.byId(payees) : {}; const categoriesById: Record<string, Category> = options.includeCategories ? groupAggregator.byId(categories) : {}; return { payeesById, categoriesById }; } async function _enrichTransactions(transactions: TransactionEntity[]): Promise<Transaction[]> { if (transactions.length === 0) { return transactions; } // # Reason: Only fetch lookup tables when transactions are missing names to avoid redundant API calls. const needsPayees = transactions.some((transaction) => Boolean(transaction.payee)); const needsCategories = transactions.some((transaction) => Boolean(transaction.category)); if (!needsPayees && !needsCategories) { return transactions; } const { payeesById, categoriesById } = await _buildTransactionLookups({ includePayees: needsPayees, includeCategories: needsCategories, }); return transactions.map((transaction: TransactionEntity) => { const payeeName = needsPayees && transaction.payee ? payeesById[transaction.payee]?.name : undefined; const categoryName = needsCategories && transaction.category ? categoriesById[transaction.category]?.name : undefined; const enriched: Transaction = { ...transaction }; if (payeeName !== undefined) { enriched.payee_name = payeeName; } if (categoryName !== undefined) { enriched.category_name = categoryName; } return enriched; }); } export async function fetchTransactionsForAccount( accountId: string, start: string, end: string ): Promise<Transaction[]> { const transactions = await getTransactions(accountId, start, end); return _enrichTransactions(transactions); } export async function fetchAllOnBudgetTransactions( accounts: Account[], start: string, end: string ): Promise<Transaction[]> { let transactions: Transaction[] = []; const onBudgetAccounts = accounts.filter((a) => !a.offbudget && !a.closed); for (const account of onBudgetAccounts) { const tx = await getTransactions(account.id, start, end); transactions = [...transactions, ...tx]; } return _enrichTransactions(transactions); } export async function fetchAllTransactions(accounts: Account[], start: string, end: string): Promise<Transaction[]> { let transactions: Transaction[] = []; for (const account of accounts) { const tx = await getTransactions(account.id, start, end); transactions = [...transactions, ...tx]; } return _enrichTransactions(transactions); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/s-stefanov/actual-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server