Beancount.Storage (beancount_ex v0.6.0)

Copy Markdown View Source

Storage and import/export for Beancount directives via Ecto.

Beancount.Storage is the high-level persistence API. It converts Beancount.Directives.* structs to Beancount.Schemas.* rows (and back), backed by Beancount.Repo. Use it to load a ledger into the database, read it back, or round-trip through .bean files.

For SQL queries over stored rows, use Beancount.Queries. For balance reports that require inventory booking, use Beancount.Report (which runs BQL through the configured engine).

Configuration

The default backend is SQLite in-memory (:memory:), which requires no setup and is cleared when the OS process exits. Keep pool_size: 1 with :memory: — each connection in the pool gets its own empty database.

config :beancount_ex, Beancount.Repo,
  database: ":memory:",
  pool_size: 1

For a persistent ledger, point :database at a file:

config :beancount_ex, Beancount.Repo, database: "ledger.db"

The migration in priv/repo/migrations creates one table per directive type; the application supervisor runs it on startup.

Public functions

FunctionDescription
import_file/1Parse a .bean file and store the directives
store/1Replace all rows with a directive list
load/0Rebuild directive structs from the database
export_file/1Render stored directives to a .bean file
clear/0Delete every row from all directive tables

store/1 and import_file/1 run in a transaction: existing rows are cleared first, then each directive is inserted in source order (file_order).

Example: store, query, export

ledger = [
  Beancount.open(~D[2026-01-01], "Assets:Bank", ["USD"]),
  Beancount.transaction(~D[2026-01-31], "*", "Employer", "Salary", [
    Beancount.posting("Assets:Bank", Decimal.new("100"), "USD"),
    Beancount.posting("Income:Salary", Decimal.new("-100"), "USD")
  ])
]

{:ok, 2} = Beancount.Storage.store(ledger)

[%Beancount.Directives.Open{account: "Assets:Bank"} | _] =
  Beancount.Storage.load()

Beancount.Queries.list_opens(prefix: "Assets")

:ok = Beancount.Storage.export_file("out.bean")

Import / export

{:ok, count} = Beancount.Storage.import_file("ledger.bean")
:ok = Beancount.Storage.export_file("out.bean")

Future backends: PostgreSQL (via postgrex), Mnesia (via ecto_mnesia).

Summary

Functions

Clear all directives from the database.

Export all directives from the database to a .bean file.

Import directives from a .bean file into the database.

Load all directives from the database.

Store a list of directives into the database.

Functions

clear()

@spec clear() :: :ok

Clear all directives from the database.

Deletes every row from all directive tables. Always returns :ok.

Examples

:ok = Beancount.Storage.clear()
[] = Beancount.Storage.load()

export_file(path)

@spec export_file(Path.t()) :: :ok | {:error, term()}

Export all directives from the database to a .bean file.

Loads every stored directive (see load/0) and renders it to Beancount text at path.

Examples

:ok = Beancount.Storage.export_file("out.bean")

import_file(path)

@spec import_file(Path.t()) :: {:ok, non_neg_integer()} | {:error, term()}

Import directives from a .bean file into the database.

Parses the file and stores the resulting directives, replacing any existing rows. Returns the number of directives stored.

Examples

{:ok, count} = Beancount.Storage.import_file("ledger.bean")
# => {:ok, 128}

load()

@spec load() :: [Beancount.Directive.t()]

Load all directives from the database.

Rebuilds Beancount.Directives.* structs from the stored rows. Dated directives are returned in date order; undated directives (option, include, plugin, pushtag, poptag) sort ahead of dated ones.

Examples

Beancount.Storage.store([Beancount.open(~D[2026-01-01], "Assets:Bank", ["USD"])])

[%Beancount.Directives.Open{account: "Assets:Bank"}] = Beancount.Storage.load()

store(directives)

@spec store([Beancount.Directive.t()]) :: {:ok, non_neg_integer()} | {:error, term()}

Store a list of directives into the database.

Runs in a transaction that first clears all existing rows, then inserts each directive into its matching table. Returns {:ok, count} where count is the number of directives actually stored; entries that are not recognized directives are skipped and not counted.

Examples

ledger = [
  Beancount.open(~D[2026-01-01], "Assets:Bank", ["USD"]),
  Beancount.transaction(~D[2026-01-31], "*", "Employer", "Salary", [
    Beancount.posting("Assets:Bank", Decimal.new("100"), "USD"),
    Beancount.posting("Income:Salary", Decimal.new("-100"), "USD")
  ])
]

{:ok, 2} = Beancount.Storage.store(ledger)