Skip to content

Architecture

Griffen Fargo edited this page May 9, 2026 · 3 revisions

Architecture

Monorepo Layout

daybook is a pnpm-workspace monorepo with five packages:

daybook/
├── packages/
│   ├── ledger/        — Core data model, types, SQLite storage
│   ├── sources/       — Source adapters (Coinbase, Kraken, Binance, generic CSV, EVM)
│   ├── classifier/    — Event classification (8-rule chain)
│   ├── tax/           — Cost basis, gain/loss, wash sale, pricing, CSV export
│   └── cli/           — CLI commands (commander + Ink)
├── docs/              — Design docs and research
├── decisions.md       — Locked-in product decisions
└── package.json       — Root workspace config

Dependency Graph

Packages depend in one direction. No cycles.

cli → tax → classifier → ledger
              sources → ledger
  • ledger is the foundation — types, DB, repo. No internal dependencies.
  • sources depends only on ledger (for RawEvent types).
  • classifier depends on ledger (consumes RawEvent, produces LedgerEntry).
  • tax depends on ledger (consumes LedgerEntry). Pricing module lives here.
  • cli depends on all four packages.

Two-Layer Event Model

daybook uses a two-layer event model that separates source data from classified output:

Layer 1: RawEvent (append-only)

Raw events are produced by source adapters and stored permanently. They represent exactly what the source reported, normalized into a common shape. Re-syncing the same data is a no-op (idempotent via INSERT OR IGNORE).

Each RawEvent has:

  • A deterministic ID ({source}:{nativeId})
  • One or more AssetLeg entries (signed amounts — positive = received, negative = spent)
  • Optional metadata: txHash, counterparty, notes

Layer 2: LedgerEntry (rebuildable)

Ledger entries are produced by the classifier from raw events. They represent the intent of a transaction (trade, transfer, income, etc.). The classifier can be re-run at any time — ledger entries are fully rebuilt from raw events + overrides.

Data Flow

Source Data → Source Adapter → RawEvent[] → Repository (append-only)
                                                ↓
                                          Classifier (8 rules + overrides)
                                                ↓
                                          LedgerEntry[] → Repository (rebuild)
                                                ↓
                                          Pricing (source → CoinGecko → manual)
                                                ↓
                                          Tax Engine (FIFO/HIFO/Specific ID)
                                                ↓
                                          Wash Sale Pass
                                                ↓
                                          CSV Export
  1. User runs daybook sync — source adapter produces RawEvent[], persisted idempotently
  2. User runs daybook classify — classifier runs 8-rule chain, produces LedgerEntry[]
  3. User runs daybook export — pricing resolves USD values, tax engine computes cost basis, CSV written

Storage

All data lives in a single SQLite database at ~/.daybook/data.db (WAL mode).

Key tables:

  • raw_events + raw_event_legs — append-only source data
  • ledger_entries — classifier output (rebuilt on each classify run)
  • classifier_overrides — user corrections that survive re-classification
  • prices — cached USD prices from the pricing chain
  • price_overrides — manual price entries

Tech Stack

  • Language: TypeScript 5.5+ (strict mode, ESM-only)
  • Runtime: Node.js >= 20
  • Build: tsup (ESM + DTS per package)
  • Test: Vitest 2 (colocated *.test.ts files)
  • Storage: better-sqlite3
  • Math: decimal.js (never floating-point for amounts)
  • CLI: commander + Ink (React for terminals)
  • EVM data: alchemy-sdk + viem
  • Validation: zod

Package Details

See Source Adapters, Classifier Rules, Tax Engine, and Pricing for detailed documentation of each package.

Clone this wiki locally