class

Finfry::Store

Inherits Reference < Object

The book's store. The truth is an append-only log of commits (finfry.log, a Keep::Log); the Database held here is the fold of that log, and finfry.json is a checkpoint of the fold as of some commit — regenerable at any time. Every mutation is an action: it is applied to the in-memory state and appended to the log by the same apply, so the fold and the live state can't disagree.

Mutations made inside changeset become one commit (kind ledger); made outside, each becomes its own commit (kind bookkeeping). Undo is a revert mark on the log — nothing is ever deleted.

An older finfry.json (no log beside it) is migrated on first open: its journal becomes commits, the rest of its state one migrated commit, and the original is kept as finfry.json.pre-log.

Constants

BOOK_FILE = "finfry.json"

The visible per-directory book file. finfry discovers it by walking up from the current directory (like git's .git), so a project/folder can hold its own ledger.

KIND_BOOKKEEPING = "bookkeeping"
KIND_LEDGER = "ledger"

Commit kinds. history shows ledger commits; bookkeeping ones (cleared marks, due staging, rules, metadata) are there with --all.

Constructors

new(path : String = Store.default_path)
Source

Class methods

apply(db : Database, a : Keep::Action, reverted : Bool = false) : Nil

Apply one action to a database. Used for the live state and for replay, so it is the definition of what each action means. For a reverted commit only the id counters advance (reverted: true), so ids are never reused even after an undo.

Source
default_path

Resolve the active ledger: an explicit FINFRY_DATA override, else the nearest book file walking up from the current directory, else the global per-user ledger.

Source
discover_book

Nearest finfry.json at or above the current directory, or nil.

Source
flat(postings : Array(Posting)) : Array(String)
Source
global_path
Source
log_path(path : String) : String

The log that goes with a checkpoint: same base name, .log.

Source
postings_of(flat : Array(String)) : Array(Posting)

Postings travel as a flat list: account, cents, account, cents, …

Source

Instance methods

account_known?(name : String) : Bool
Source
account_meta(account : String) : Hash(String, String)
Source
account_policy
Source
add_recurring_rule(description : String, cadence : String, start_date : String, postings : Array(Posting), kind : String = "fixed") : RecurringRule
Source
balances(prefix : String | Nil = nil, up_to : String | Nil = nil) : Hash(String, Int64)

Net balance of every account (optionally restricted to a subtree, and/or to transactions on or before up_to). Returns account => signed cents.

Source
budgets
Source
changeset(summary : String, at : String, kind : String = KIND_LEDGER, & : -> T) : T forall T

Run a block as one commit. Nested calls join the enclosing one, so an AI plan's many operations collapse into one undo unit. Nothing is written if the block changed nothing or raised.

Source
changesets(all : Bool = false) : Array(Changeset)

Ledger commits (what history shows), oldest first, as Changeset views: id = the commit's sequence number. With all, every commit — bookkeeping (cleared marks, due staging, rules, metadata) and the undo marks themselves — so the log can be read end to end.

Source
cleared?(account : String, id : Int32) : Bool
Source
cleared_balance(account : String) : Int64

Balance of the staged tier (cleared-but-not-committed).

Source
cleared_ids(account : String) : Array(Int32)

Transaction ids marked cleared against account's statement.

Source
deactivate_rule(id : Int32) : Bool
Source
declare_account(name : String) : Bool

Declare an account in the chart. Returns false if already declared.

Source
declared_accounts

Accounts explicitly declared in the chart.

Source
delete_transaction(id : Int32) : Transaction | Nil

Remove a transaction by id. Returns the deleted record, or nil if absent.

Source
due_entries
Source
edit_due(id : Int32, date : String, description : String, postings : Array(Posting)) : Nil

Adjust an entry before posting; it's marked ok.

Source
example?
Source
generate_due(today : String) : Int32

Materialize every occurrence due up to today into the queue, advancing each rule's cursor so nothing is generated twice. What was generated — including a computed interest charge — is what's logged, so replay never recomputes it. Returns how many were added.

Source
known_accounts

Declared ∪ used — the accounts finfry treats as known. Feeds the AI's chart context, the accounts listing, and completions.

Source
last_reconciliation(account : String) : Reconciliation | Nil

The most recent finalized reconciliation for an account, if any.

Source
mark_example(flag : Bool = true) : Nil
Source
occurrences_due(today : String) : Int32

How many occurrences generate_due would materialize as of today, without touching anything — so a read-only view can say "N new have come due" and offer the catch-up as a deliberate step.

Source
origin

Where commits are attributed to: cli, web, mcp, ai.

Source
origin=(origin : String)

Where commits are attributed to: cli, web, mcp, ai.

Source
path
Source
reconcile!(account : String, statement : Int64, date : String, statement_date : String | Nil = nil) : Int32

Finalize a reconciliation: move every staged-cleared transaction into the committed tier and record the statement it was balanced against. The caller verifies the balance matches first. Returns the number locked in.

Source
reconciled?(account : String, id : Int32) : Bool
Source
reconciled_balance(account : String) : Int64

Balance of the committed tier (locked by past reconciliations).

Source
reconciled_ids(account : String) : Array(Int32)

Committed (reconciled) tier.

Source
reconciliations(account : String) : Array(Reconciliation)

Every finalized reconciliation for an account, oldest first.

Source
record(date : String, description : String, postings : Array(Posting), recurrence : String | Nil = nil) : Transaction

Build, validate, persist, and return a transaction. Raises Error if the postings don't balance.

Source
recurring_rules
Source
redo_available?
Source
redo_last

Bring back the change undo just removed — only while it is the latest thing in the log; any new change invalidates it.

Source
refresh

Pick up commits another process appended (the CLI or MCP alongside serve). Returns true if anything new was folded in.

Source
remove_budget(account : String) : Bool
Source
remove_due_entries(ids : Array(Int32)) : Nil
Source
rename_account(from : String, to : String) : Int32

Rewrite every posting on from to to (also updating the chart and any budget keyed on it). Doubles as a merge when to already exists. Returns the number of postings rewritten.

Source
reverse(id : Int32, at : String, date : String) : Changeset | Nil

Correct an older change the proper accounting way: append a reversing entry. The original is never removed — transactions it added are negated by mirror-image postings, budget changes restored. Returns the reversing changeset, nil if id is unknown, or raises if it's already reversed.

Source
reversed?(id : Int32) : Bool

True if a reversing entry already undid changeset id.

Source
set_account_meta(account : String, key : String, value : String) : Nil
Source
set_account_policy(policy : String) : Nil
Source
set_budget(account : String, limit : Int64) : Nil
Source
set_cleared(account : String, ids : Array(Int32), cleared : Bool) : Int32

Mark (or unmark) transactions as cleared against account. Returns the number of ids whose state actually changed.

Source
set_due_status(ids : Array(Int32), status : String) : Nil
Source
spent(account : String, month : String) : Int64

Net movement into an account subtree within a "YYYY-MM" month. For an Expenses account this is the amount spent.

Source
touch

Make sure the book exists on disk (its log and checkpoint), e.g. init.

Source
transactions
Source
undeclare_account(name : String) : Bool

Remove an account from the chart. Returns false if it wasn't declared. (If postings still reference it, it stays "known" via use.)

Source
undo_last

Undo the most recent ledger change by marking it reverted — as if it never happened, without erasing it from the log. Returns it, or nil if there's nothing to undo.

Source
unset_account_meta(account : String, key : String) : Bool

Remove a metadata key. Returns false if it wasn't set.

Source
used_accounts

Every distinct account a posting actually references, sorted.

Source
version

The commit sequence the in-memory state is folded to.

Source