npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zuccs/beancount-lite

v0.1.0

Published

Lightweight TypeScript library that parses and validates beancount-format plain-text journals. Runs in Cloudflare Workers. Zero dependencies.

Downloads

13

Readme

beancount-lite

Lightweight TypeScript library that parses and validates Beancount-format plain-text journals. Runs in Cloudflare Workers. Zero dependencies.

This project implements a subset of the Beancount plain-text accounting language created by Martin Blais. Beancount is the original system — a powerful, mature double-entry bookkeeping tool written in Python. All credit for the language design, syntax specification, and accounting semantics belongs to Martin and the Beancount community. This library is an independent TypeScript implementation that reads the same file format; it is not affiliated with or endorsed by the Beancount project.

If you're new to plain-text accounting, start with the real thing: beancount.github.io/docs

Why this exists

We needed to parse and validate Beancount journals inside Cloudflare Workers — no filesystem, no Python runtime, no binary dependencies. This library takes a journal string in, gives structured data out.

It deliberately implements only the subset of Beancount we need. If you need the full language (commodity prices, cost basis, lot tracking, plugins, queries), use Beancount itself or Fava.

Install

npm install beancount-lite

Quick start

import { parse, validate, balances, balanceReport, formatTransaction } from "beancount-lite";

const journal = parse(`
2024-01-01 open Assets:Bank:Checking AUD
2024-01-01 open Expenses:Food
2024-01-01 open Income:Salary
2024-01-01 open Equity:Opening-Balances

2024-01-01 * "Opening Balance"
  Assets:Bank:Checking          5000.00 AUD
  Equity:Opening-Balances      -5000.00 AUD

2024-01-15 * "Woolworths" "Weekly groceries" #household
  Expenses:Food                  125.50 AUD
  Assets:Bank:Checking          -125.50 AUD

2024-01-31 * "Employer" "January salary"
  Assets:Bank:Checking          4500.00 AUD
  Income:Salary                -4500.00 AUD

2024-02-01 balance Assets:Bank:Checking  9374.50 AUD
`);

// Validate — returns errors (empty array = all good)
const errors = validate(journal);
console.log(errors); // []

// Query balances
const bal = balances(journal);
console.log(bal.get("Assets:Bank:Checking")); // 9374.50
console.log(bal.get("Expenses:Food"));        // 125.50

// Balance report with hierarchy rollup
const report = balanceReport(journal, { account: "Expenses" });
// Expenses        → 125.50
// Expenses:Food   → 125.50

API

parse(text: string): Journal

Parse Beancount journal text into structured data.

const journal = parse(journalText);
// journal.transactions    — Transaction[]
// journal.balanceDirectives — BalanceDirective[]
// journal.accountDirectives — AccountDirective[] (open/close)

validate(journal: Journal, options?): ValidationError[]

Validate a parsed journal. Returns an array of errors (empty = valid).

const errors = validate(journal);
// Options:
//   strict?: boolean    — require all accounts to be declared with `open` (default: auto-detected)
//   duplicates?: boolean — check for duplicate transactions (default: true)

Checks performed:

  • Every transaction balances to zero (or has exactly one auto-balanced posting)
  • Balance assertions match running totals (beginning-of-day semantics, per the Beancount spec)
  • All accounts are declared with open directives (when open directives are present)
  • No duplicate transactions (same date + narration + amount)

balances(journal: Journal, filter?): Map<string, number>

Compute per-account balances, optionally filtered.

const bal = balances(journal, {
  dateFrom: "2024-01-01",
  dateTo: "2024-01-31",
  account: "Expenses",        // prefix match
});

balanceReport(journal: Journal, filter?): Map<string, number>

Like balances() but rolls up totals through the account hierarchy.

const report = balanceReport(journal, { account: "Expenses" });
// Expenses:Property:Utilities:Electricity → 150
// Expenses:Property:Utilities:Water       → 80
// Expenses:Property:Utilities             → 230   (rolled up)
// Expenses:Property                       → 230   (rolled up)
// Expenses                                → 230   (rolled up)

filterTransactions(journal: Journal, filter?): Transaction[]

Filter transactions by date range and/or account prefix.

formatTransaction(tx: Transaction): string

Format a Transaction object as valid Beancount journal text.

const text = formatTransaction({
  date: "2024-01-15",
  flag: "*",
  payee: "Woolworths",
  narration: "Weekly groceries",
  postings: [
    { account: "Expenses:Food", amount: 125.50, currency: "AUD" },
    { account: "Assets:Bank:Checking", amount: -125.50, currency: "AUD" },
  ],
  tags: new Set(["household"]),
  links: new Set(),
  meta: {},
  line: 0,
});

formatBalanceReport(balanceMap: Map<string, number>, currency?): string

Format a balance map as aligned plain text.

checkAssertion(journal, account, expectedBalance, asOfDate, currency?): boolean

Check a single balance assertion against journal state.

Beancount syntax supported

This library parses the following Beancount directives and syntax:

| Feature | Supported | Example | |---|---|---| | Transactions | ✅ | 2024-01-15 * "Payee" "Narration" | | Flags | ✅ | * (cleared), ! (pending) | | txn keyword | ✅ | 2024-01-15 txn "Narration" | | Payee + narration | ✅ | "Payee" "Narration" | | Narration only | ✅ | "Just a narration" | | Bare transactions | ✅ | 2024-01-15 * (no narration) | | Postings with amounts | ✅ | Assets:Bank:Checking 100.00 AUD | | Auto-balanced postings | ✅ | Assets:Bank:Checking (amount inferred) | | open directive | ✅ | 2024-01-01 open Assets:Bank AUD | | close directive | ✅ | 2024-12-31 close Assets:Bank | | balance directive | ✅ | 2024-02-01 balance Assets:Bank 1000.00 AUD | | Tags | ✅ | #tag-name | | Links | ✅ | ^link-name | | Metadata | ✅ | key: "value" (indented under directive) | | Comments | ✅ | ; comment text | | option directive | ✅ | Recognised and skipped | | ISO dates | ✅ | YYYY-MM-DD and YYYY/MM/DD | | Account types | ✅ | Assets, Liabilities, Equity, Income, Expenses |

Not supported (by design)

These Beancount features are intentionally excluded to keep the library small:

  • Multi-currency / commodity prices / cost basis ({...}, @ price)
  • Lot tracking and inventory booking
  • pad directive
  • note, document, event, query, custom directives
  • pushtag / poptag
  • include and plugin directives
  • Arithmetic expressions in amounts
  • The full Beancount query language (BQL)
  • Budget tracking

Data types

type Transaction = {
  line: number;                    // Source line number (1-based)
  date: string;                    // "2024-01-15"
  flag: "*" | "!";                 // Cleared or pending
  payee?: string;                  // "AGL Energy"
  narration: string;               // "Electricity bill"
  postings: Posting[];
  tags: Set<string>;               // #tag
  links: Set<string>;              // ^link
  meta: Record<string, string>;    // key: "value"
  comment?: string;                // ; inline comment
};

type Posting = {
  account: string;                 // "Expenses:Property:Utilities:Electricity"
  amount?: number;                 // Undefined = auto-balanced
  currency?: string;               // "AUD"
};

type Journal = {
  transactions: Transaction[];
  balanceDirectives: BalanceDirective[];
  accountDirectives: AccountDirective[];
};

Environment

  • Pure TypeScript, no Node-specific APIs
  • Runs in Cloudflare Workers (no fs, no path, no process)
  • Input is always a string
  • Zero external runtime dependencies
  • ESM and CJS builds included

Credits

Beancount was created by Martin Blais and is the foundation this library builds on. The language syntax, accounting semantics (balance assertion timing, transaction balancing rules, account hierarchy, auto-balanced postings), and overall design philosophy all come from Beancount.

This library is an independent implementation. It is not affiliated with, endorsed by, or a fork of Beancount.

License

MIT