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

beaver-accounting

v0.1.6

Published

Core library for Beaver Accounting - A double-entry accounting system

Readme

beaver-accounting

A production-ready TypeScript library implementing double-entry accounting backed by PostgreSQL.

npm version npm downloads license TypeScript


Features

  • Double-entry bookkeeping with debit/credit validation
  • Hierarchical chart of accounts (Asset, Liability, Equity, Revenue, Expense)
  • Fiscal period management with open/closed status
  • Journal entry posting with automatic entry numbering
  • Reversal entries linked back to originals
  • General ledger, account balances, and trial balance
  • Balance sheet and income statement generation
  • Retained earnings calculation
  • Full audit trail with actor and action tracking
  • PostgreSQL-backed with transactional writes and connection pooling
  • CLI for running database migrations

Prerequisites

  • Node.js >= 20
  • PostgreSQL (any recent version)

Installation

npm install beaver-accounting

Setup

Configure the database connection via environment variables:

| Variable | Default | Description | |-----------------------------|----------------------|------------------------------------------| | PGHOST | localhost | PostgreSQL host | | PGPORT | 5432 | PostgreSQL port | | PGDATABASE | beaver_accounting | Database name | | PGUSER | postgres | Database user | | PGPASSWORD | (empty) | Database password | | PGSSL | false | Enable SSL (true / false) | | PGSSL_REJECT_UNAUTHORIZED | true | Reject invalid SSL certs (set false in dev) | | PGMAX_CONNECTIONS | 10 | Connection pool size | | PG_CONNECTION_TIMEOUT | 30000 | Connection timeout in ms | | PG_QUERY_TIMEOUT | 0 | Query timeout in ms (0 = disabled) | | PG_RETRY_ATTEMPTS | 3 | Connection retry attempts | | PG_RETRY_DELAY | 1000 | Delay between retries in ms |


Database Migrations

Run migrations before using the library. The CLI reads connection settings from the environment variables above.

# Apply all pending migrations
npx beaver-accounting migrate

# Check migration status
npx beaver-accounting migrate:status

# Create a new migration file
npx beaver-accounting migrate:create <name>

# Rollback the most recent migration
npx beaver-accounting migrate:rollback

# Rollback multiple migrations
npx beaver-accounting migrate:rollback --steps 3

Quick Start

import {
  PostgreSQLConnection,
  getDatabaseConfig,
  AccountingServiceFactory,
  AccountType,
  LineType,
  PeriodStatus,
  // Types
  type Account,
  type FiscalPeriod,
  type JournalEntryWithLines,
  type BalanceSheet,
  type IncomeStatement,
} from 'beaver-accounting';

// 1. Connect to the database
const connection = new PostgreSQLConnection(getDatabaseConfig());
await connection.connect();

// 2. Wire all services from a single factory call
const factory = new AccountingServiceFactory(connection);
const { accountService, journalEntryService } = factory.createAll({
  withAuditService: true,
});

// 3. Create accounts
const cash = await accountService.createAccount({
  code: '1000',
  name: 'Cash',
  type: AccountType.Asset,
});

const revenue = await accountService.createAccount({
  code: '4000',
  name: 'Revenue',
  type: AccountType.Revenue,
});

// 4. Post a journal entry (debits must equal credits)
const entry = await journalEntryService.postEntry(
  {
    fiscalPeriodId: 'your-period-uuid',
    entryDate: new Date(),
    description: 'Cash sale',
    lines: [
      { accountId: cash.id, lineType: LineType.Debit, amount: '500.00' },
      { accountId: revenue.id, lineType: LineType.Credit, amount: '500.00' },
    ],
  },
  'user-id', // optional actor ID for audit trail
);

console.log(entry.entryNumber); // e.g. "JE-20260302-0001"

// 5. Reverse a journal entry (if needed)
const reversal = await journalEntryService.reverseEntry(
  entry.id,
  { reason: 'Data entry error', reversalDate: new Date() },
  'user-id'
);

await connection.disconnect();

Money values are always string (e.g. "500.00"). The library uses big.js internally to avoid floating-point errors. Never pass number for monetary amounts.

Connection Health & Pool Stats

// Check if the connection is healthy
const isHealthy = await connection.isHealthy(); // true or false

// Get connection pool statistics
const stats = connection.getPoolStats();
// { totalCount: 10, idleCount: 8, waitingCount: 0 }

Fiscal Period Management

Fiscal periods are essential for organizing accounting data and preventing changes to historical records.

const { periodRepository } = factory.createAll();

// Create a fiscal period
const period = await periodRepository.save({
  name: 'March 2026',
  startDate: new Date('2026-03-01'),
  endDate: new Date('2026-03-31'),
  status: PeriodStatus.Open,
});

// Close a period (prevents posting to it)
await periodRepository.close(period.id, 'user-id');

// Reopen if needed
await periodRepository.reopen(period.id, 'user-id');

// Find periods
const openPeriods = await periodRepository.findOpenPeriods();
const periodByDate = await periodRepository.findByDate(new Date('2026-03-15'));
const allPeriods = await periodRepository.findAll();

Core Concepts

| Concept | Description | |---|---| | Account | A node in the chart of accounts. Has a type (Asset, Liability, Equity, Revenue, Expense) and a normalBalance auto-derived from the type. Accounts can be nested via parentId. | | Journal Entry | A balanced set of debit and credit lines. Total debits must equal total credits. Each entry is assigned a sequential entryNumber. | | Fiscal Period | A named date range (e.g. "March 2026") with a status of OPEN or CLOSED. Posting to a closed period throws PeriodClosedError. | | Audit Trail | Every mutating action (account created, entry posted, entry reversed) can be recorded with actor ID, actor type, and metadata. Enable with withAuditService: true. |


Services Overview

| Service | Purpose | |---|---| | AccountService | Create, update, deactivate, and query accounts in the chart of accounts | | JournalEntryService | Post new entries, reverse entries, query by period / date range / account | | LedgerService | Retrieve general ledger lines for an account | | BalanceService | Calculate account balances for a period or date range | | TrialBalanceService | Generate a trial balance report across all accounts | | BalanceSheetService | Produce a structured balance sheet (assets, liabilities, equity) | | IncomeStatementService | Produce a structured income statement (revenue, expenses, net income) | | RetainedEarningsService | Calculate retained earnings across fiscal periods | | AuditService | Record and query audit log entries |

All services are available via AccountingServiceFactory.createAll(). Internally, shared instances are reused so that services composing others (e.g. BalanceSheetService depends on BalanceService) do not duplicate work.


API Reference

AccountService Methods

| Method | Description | |--------|-------------| | createAccount(options, actorId?) | Create a new account | | updateAccount(id, options, actorId?) | Update account name, description, parent | | deactivateAccount(id, actorId?) | Deactivate (requires no journal lines or active children) | | reactivateAccount(id, actorId?) | Reactivate an inactive account | | findById(id) / findByCode(code) | Look up accounts | | findAll(options?) / findActive(options?) | List all or active accounts | | findByType(type, options?) | Filter by account type | | getAccountTree() | Get hierarchical tree of all accounts | | getChildren(parentId) / getRootAccounts() | Navigate hierarchy | | getDescendants(id) / getAncestors(id) | Get all below/above in tree | | moveSubtree(id, newParentId, moveChildren?, actorId?) | Move account branch |

JournalEntryService Methods

| Method | Description | |--------|-------------| | postEntry(dto, actorId?) | Post a new balanced entry | | reverseEntry(id, dto, actorId?) | Create reversing entry | | findById(id) / findByEntryNumber(number) | Look up entries | | findByPeriod(periodId) | Entries in a fiscal period | | findByDateRange(start, end) | Entries in date range | | findByAccount(accountId) | All entries affecting an account |

LedgerService Methods

| Method | Description | |--------|-------------| | getAccountLedger(accountId) | Full ledger with running balance | | getAccountLedgerByPeriod(accountId, periodId, openingBalance?) | Period-specific ledger | | getAccountLedgerByDateRange(accountId, start, end, openingBalance?) | Date range ledger |

BalanceService Methods

| Method | Description | |--------|-------------| | getAccountBalance(accountId) | Current balance (all time) | | getAccountBalanceAtDate(accountId, date) | Balance as of a date | | getAccountBalanceByPeriod(accountId, periodId) | Balance for a period | | getAllAccountBalances(asOfDate?) | Balances for all active accounts | | getBalancesByType(type, asOfDate?) | Filter by account type | | getBalanceSummary(asOfDate?) | Complete summary with totals |

Report Services

| Service | Method | Description | |---------|--------|-------------| | TrialBalanceService | generateTrialBalance() | Current trial balance | | | generateTrialBalanceAtDate(date) | As of specific date | | | generateTrialBalanceByPeriod(periodId) | For a fiscal period | | BalanceSheetService | generate(asOfDate, options?) | Full balance sheet | | IncomeStatementService | generate(startDate, endDate, options?) | Income statement | | RetainedEarningsService | calculate(asOfDate) | Retained earnings total | | | getBreakdown(asOfDate) | Breakdown by period |

AuditService Methods

| Method | Description | |--------|-------------| | record(dto) / recordStrict(dto) | Log an audit entry | | getEntityHistory(entityType, entityId, limit?) | History for an entity | | getActorHistory(actorId, limit?) | History by actor | | getActionsByDateRange(start, end, limit?) | History in date range | | getCorrelatedEntries(correlationId) | Linked entries (e.g., reversals) | | query(options) | Flexible query with filters |


Report Generation Examples

const { trialBalanceService, balanceSheetService, incomeStatementService, retainedEarningsService } =
  factory.createAll();

// Trial Balance
const trialBalance = await trialBalanceService.generateTrialBalanceAtDate(new Date());
console.log(trialBalance.isBalanced, trialBalance.totalDebits);

// Balance Sheet
const balanceSheet = await balanceSheetService.generate(new Date(), {
  includeZeroBalances: false,
});
console.log(balanceSheet.totalAssets, balanceSheet.totalLiabilitiesAndEquity);

// Income Statement
const incomeStatement = await incomeStatementService.generate(
  new Date('2026-01-01'),
  new Date('2026-03-31'),
);
console.log(incomeStatement.netIncome, incomeStatement.isLoss);

// Retained Earnings
const retainedEarnings = await retainedEarningsService.getBreakdown(new Date());
console.log(retainedEarnings.amount, retainedEarnings.breakdown);

Type Definitions

| Type | Description | |------|-------------| | Account | { id, code, name, type, normalBalance, parentId, isActive, ... } | | JournalEntry | { id, entryNumber, fiscalPeriodId, entryDate, description, ... } | | JournalEntryWithLines | Journal entry with lines: JournalEntryLine[] | | FiscalPeriod | { id, name, startDate, endDate, status, closedAt, closedBy } | | AccountBalance | { accountId, balance, balanceType, totalDebits, totalCredits } | | AccountLedger | { entries: LedgerEntry[], openingBalance, closingBalance, ... } | | TrialBalance | { lines, totalDebits, totalCredits, isBalanced, ... } | | BalanceSheet | { currentAssets, fixedAssets, liabilities, equity, totals, ... } | | IncomeStatement | { revenue, expenses, totalRevenue, totalExpenses, netIncome, ... } | | AuditLog | { actorId, action, entityType, entityId, before, after, ... } |


Account Code Conventions

The library uses these account code ranges for automatic categorization in financial reports:

| Range | Category | |-------|----------| | 1000-1499 | Current Assets | | 1500+ | Fixed Assets | | 2000-2499 | Current Liabilities | | 2500+ | Long-term Liabilities | | 3000+ | Equity | | 4000+ | Revenue | | 5000+ | Expenses |


CLI Reference

beaver-accounting <command>

| Command | Arguments / Options | Description | |---|---|---| | migrate | — | Apply all pending migrations | | migrate:status | — | Show applied and pending migrations | | migrate:create | <name> | Scaffold a new .sql migration file | | migrate:rollback | --steps <n> (default 1) | Roll back the last n migrations |


Error Handling

All errors are named exports that extend AccountingError. Import them directly for typed catch blocks.

import {
  PeriodClosedError,
  BalanceError,
  AccountNotFoundError,
  JournalEntryNotFoundError,
  ValidationError,
  DuplicateAccountCodeError,
} from 'beaver-accounting';

try {
  await journalEntryService.postEntry(dto);
} catch (err) {
  if (err instanceof PeriodClosedError) {
    // Attempted to post into a closed fiscal period
  } else if (err instanceof BalanceError) {
    // Debits do not equal credits
  } else if (err instanceof ValidationError) {
    // Field-level validation failure — err.errors contains details
  } else {
    throw err;
  }
}

All exported error classes:

AccountingError · BalanceError · AccountNotFoundError · PeriodClosedError · PeriodNotFoundError · DuplicateEntryError · ValidationError · DuplicateAccountCodeError · AccountHasJournalLinesError · AccountHasChildrenError · CircularReferenceError · ParentNotFoundError · ParentInactiveError · JournalEntryNotFoundError · EntryAlreadyReversedError


License

MIT