The schema lives in packages/infra/db/src/schema/. One file per
entity bundle; schema/index.ts is the barrel. Migrations live in
packages/infra/db/src/migrations/ and are registered in
meta/_journal.json.
Every relevant concept also has its own page in
Concepts . This page is the dense
schema-only summary.
Table Purpose Concept usersPer-tenant user rows. — sessionsBetter-Auth sessions. — user_integration_credentialsEncrypted exchange / brokerage API keys. AES-256-GCM with ENCRYPTION_KEY. Tier model
Table Purpose Concept institution_typesbank, broker, crypto_exchange, crypto_wallet, investment_fund, private_equity, real_estate, other.Accounts institutionsCatalogue of financial entities — shared across users (manual institution is per-user). institution_blockchain_mappingsMaps blockchain-typed institutions to (chainId, chainType). account_typeschecking, savings, investment, wallet, etc.accountsPer-user container for holdings at one institution. metadata jsonb holds wallet addresses / chain data.
Table Purpose Concept token_typesfiat, crypto, public-stock, private-company, other.Tokens tokensTradeable assets. Unique key: (symbol, typeId, marketSegment) (migration 0055). providerMetadata jsonb is namespaced per provider. unpriceableUntil is the price-backfill cooldown gate. token_pricesHistorical prices. Unique key: (tokenId, baseTokenId, timestamp, granularity). Granularity: daily, intraday, tx-exact. No USD-canonical column. Pricing token_price_edit_historyAppend-only log of manual price edits for private-company / other tokens. Manual assets
Table Purpose Concept holdingsAtomic position. balance decimal string. source (manual / blockchain / provider name). externalId for sync matching. isHidden / isActive flags. Holdings holding_transactionsAppend-only ledger. Every economic event. Loose kind (buy, sell, deposit, withdraw, transfer_in, transfer_out, swap_in, swap_out, fee, reward, interest, airdrop, opening_balance, unknown). Signed quantity. priceNative in native quote currency. transferGroupId / swapGroupId for pair-linking. Dedup key: (holdingId, source, externalId).Transactions holding_balance_observationsAppend-only point-in-time balance anchors. Sources: sync-capture, statement-close, screenshot, user-entered, manual-correction. Dedup: (holdingId, observedAt, source). Observations holding_coveragePer-holding metadata. First/last tx and observation, txSources, hasCompleteTxHistory, openingBalanceQuantity (synthesised by reconciliation). Observations holding_apy_configsPer-holding yield rules. annualRatePct, payoutFrequency, day-of-week/month/year. Drives the apy-payouts cron. APY & yield holding_exclusionsUser-managed exclusion rules per holding. Holdings
Table Purpose Concept vaultsSavings goals. targetAmount + currencyId. currentAmount denormalised sum. Vaults vault_holdingsJunction. percentage (1–100). Unique (vaultId, holdingId). groupsUser-defined tags. Hex color, displayOrder. Groups holding_groupsJunction. Unique (holdingId, groupId). account_groupsJunction. Unique (accountId, groupId).
Table Purpose Concept portfolio_value_dailyDaily cache. PK (userId, scopeKind, scopeId, snapshotDate, baseCurrencyId). scopeKind: user, institution, account, holding. coverageQuality: full / partial / estimated / unknown. costBasis / realizedPnl / unrealizedPnl decimal strings. Rollup
Table Purpose user_jobsUser-initiated async job state (screenshot parses, imports, deletes). job_heartbeatsPer-job heartbeat rows; drives the job-heartbeat-probe. admin_audit_logOperator actions on api admin endpoints. user_walletsUser-tracked blockchain wallet addresses. cloud_api_keys, cloud_usage_eventsTier 2/3 cloud-management (CLOUD_MANAGEMENT_ENABLED=true).
The schema uses three ON DELETE behaviours deliberately:
Behaviour Used for Rationale CASCADEuserId references; accountId from holdings; holdingId from ledger / observations / coverage / APY configs.Deleting a user / account should remove their data. RESTRICTtokenId from holdings; baseTokenId from token_prices.Refuse to delete a token / base currency that still has live references. SET NULLInformational token references on transactions: priceNativeTokenId, counterTokenId, counterPriceNativeTokenId, feeTokenId. If a token is dedup-merged (migrations 0006 / 0007), null the reference rather than block the merge.
These are never updated, never deleted in normal operation:
holding_transactions (one documented exception: the
synthesised opening_balance row, updated in place by the
reconciliation flow — at most one per holding per cycle).
holding_balance_observations.
token_price_edit_history.
admin_audit_log.
cloud_usage_events.
See Why an append-only ledger .
Every table file exports its row type via Drizzle’s inference:
export type Holding = typeof holdings . $inferSelect ;
export type NewHolding = typeof holdings . $inferInsert ;
These are the types you reach for in services and repositories.
The codebase deliberately never hand-writes a row interface.
packages/infra/db/src/schema/ — all schema files + the
index.ts barrel.
packages/infra/db/src/migrations/ — generated and hand-written
SQL.
packages/infra/db/src/migrate.ts — the runner.
packages/infra/db/src/connection.ts — postgres.js client setup.
packages/infra/db/src/BaseRepository.ts — shared repository
helpers.