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

@remade/nestjs-ledger

v0.1.0

Published

Double-entry accounting ledger module for NestJS with TypeORM

Readme

Ledger Module

Double-entry accounting (DEA) library module for NestJS. Provides a complete financial ledger with support for payment lifecycles (authorize, capture, void, settle, refund), deposits, withdrawals, wallet-to-wallet transfers, cross-currency FX conversion, hierarchical chart of accounts, a configurable fee engine, transactional event publishing, and audit/reconciliation tools.

Installation

Import LedgerModule into your host application:

import { LedgerModule } from '@app/ledger';

@Module({
  imports: [
    LedgerModule.forRoot({
      controllers: { enabled: true },
      connectionName: 'default',
      holdTtlDefaultSeconds: 604800, // 7 days
    }),
  ],
})
export class AppModule {}

Documentation

Full documentation is available in docs/:

| Document | Description | |---|---| | Overview & Quick Start | Architecture, principles, module options | | Entities | All 11 entities with fields, relationships, constraints | | Payment Flows | Payment lifecycle, deposit, withdraw, transfer, FX conversion | | Fee Engine | Fee resolution, calculation methods, splits, tiered volume | | Events | Transactional outbox pattern, event types, consumer guidance | | Audit & Reconciliation | Trial balance, balance reconstruction, account statements | | API Reference | All REST endpoints with request/response examples |

Directory Structure

libs/ledger/src/
  index.ts                          # Barrel exports
  ledger.module.ts                  # Dynamic module (forRoot / forRootAsync)
  ledger.constants.ts               # Injection tokens

  enums/                            # 10 enum files (account type, transaction status, etc.)
  entities/                         # 11 TypeORM entities
  dto/                              # 20 validation DTOs grouped by resource
  services/                         # 13 business logic services
  engine/                           # 3 pure calculation/logic classes (no DB)
  controllers/                      # 6 REST API controllers

Entities

| Entity | Table | Description | |---|---|---| | Currency | ledger_currencies | Currency definitions (fiat + crypto). PK is code (VARCHAR). | | Account | ledger_accounts | Chart of accounts with hierarchy, 7 account types, materialized balances (available/held/pending). | | JournalEntry | ledger_journal_entries | Immutable double-entry records. Each entry debits one account and credits another. | | Transaction | ledger_transactions | Payment lifecycle tracking with state machine and FX conversion fields. | | ActiveHold | ledger_active_holds | Authorization holds with TTL and partial capture support. | | FeeSchedule | ledger_fee_schedules | Fee configuration with 4 calculation methods, priority resolution, and fee splitting. | | FeeTier | ledger_fee_tiers | Volume-based tier brackets for tiered fee schedules. | | ExchangeRate | ledger_exchange_rates | Currency pair rates with validity periods and auto-computed inverse. | | HoldTtlPolicy | ledger_hold_ttl_policies | Priority-based hold TTL configuration (global/merchant/type/currency). | | AccountStatement | ledger_account_statements | Period-end balance snapshots for audit. | | LedgerEvent | ledger_events | Transactional outbox for reliable event publishing. |

All monetary amounts use numeric(78,0) (BigInt-compatible), stored as string in TypeScript.

API Endpoints

Accounts (/accounts)

| Method | Route | Description | |---|---|---| | POST | /accounts | Create a new account | | GET | /accounts | List accounts (paginated, filterable) | | GET | /accounts/:id | Get account by ID | | GET | /accounts/:id/balance | Get account balances | | GET | /accounts/:id/subtree | Get subtree balance (recursive roll-up) | | GET | /accounts/owner/:ownerType/:ownerId | Get accounts by owner | | POST | /accounts/:id/deactivate | Deactivate an account |

Payments (/payments)

| Method | Route | Description | |---|---|---| | POST | /payments/authorize | Create authorization (places hold) | | POST | /payments/:id/capture | Capture authorized amount (full or partial) | | POST | /payments/:id/void | Void authorization (releases hold) | | POST | /payments/:id/settle | Settle captured amount to merchant | | POST | /payments/:id/refund | Refund settled amount (full or partial) | | POST | /payments/deposit | Fund a wallet from house cash | | POST | /payments/withdraw | Withdraw from wallet to house cash | | POST | /payments/transfer | Wallet-to-wallet same-currency transfer | | POST | /payments/convert | Cross-currency FX conversion |

Fee Schedules (/fee-schedules)

| Method | Route | Description | |---|---|---| | POST | /fee-schedules | Create a fee schedule | | GET | /fee-schedules | List fee schedules (paginated) | | GET | /fee-schedules/:id | Get fee schedule with tiers | | PATCH | /fee-schedules/:id | Update fee schedule | | POST | /fee-schedules/:id/deactivate | Deactivate a fee schedule | | POST | /fee-schedules/:id/tiers | Add a volume tier | | DELETE | /fee-schedules/tiers/:tierId | Remove a volume tier | | POST | /fee-schedules/resolve | Preview fee resolution and calculation |

Exchange Rates (/exchange-rates)

| Method | Route | Description | |---|---|---| | GET | /exchange-rates/:from/:to | Get current exchange rate | | GET | /exchange-rates/:from/:to/history | Get rate history (paginated) |

Hold TTL Policies (/hold-policies)

| Method | Route | Description | |---|---|---| | POST | /hold-policies | Create a TTL policy | | GET | /hold-policies | List policies (paginated) | | GET | /hold-policies/:id | Get policy by ID | | PUT | /hold-policies/:id | Update a policy | | POST | /hold-policies/:id/deactivate | Deactivate a policy |

Admin / Audit

| Method | Route | Description | |---|---|---| | GET | /ledger/trial-balance | SUM(debits) vs SUM(credits) per currency | | GET | /ledger/accounts | Full chart of accounts tree | | GET | /ledger/accounts/:id/subtree | Subtree roll-up balance | | POST | /ledger/reconcile | Trigger balance reconstruction | | GET | /ledger/fx-suspense-check | Check FX suspense zero-balance | | POST | /admin/statements/generate | Generate period-end statement | | GET | /admin/events | List ledger events (filtered) | | POST | /admin/events/:id/replay | Republish an event |

Exported Services

| Service | Key Methods | |---|---| | CurrencyService | create, findByCode, findAll, deactivate | | AccountService | create, findById, findAll, findByOwner, getBalance, getSubtreeBalance, deactivate | | JournalEntryService | createEntry (within a transaction manager) | | TransactionService | create, updateStatus, findById, findByIdempotencyKey | | PaymentService | authorize, capture, void, settle, refund, deposit, withdraw, transfer | | HoldService | createHold, captureHold, releaseHold, findActiveByTransaction | | FeeService | create, update, findById, findAll, deactivate, addTier, removeTier, resolve, calculateFee, applyFee | | ExchangeRateService | create, findCurrentRate, findAll, getHistory | | HoldTtlPolicyService | create, update, findById, findAll, deactivate, resolveTtl | | LedgerEventService | emit, findPending, markPublished, markFailed, findByAggregate, findAll | | AccountStatementService | generate, findByAccount, findByPeriod | | ConversionService | convert | | AuditService | trialBalance, reconstructBalance, fxSuspenseCheck, chartOfAccounts |

Key Design Decisions

  • Immutable journal entries: Once created, journal entries are never modified or deleted. Corrections are made via reversal entries.
  • Materialized balances: Account balances (available, held, pending) are stored directly on the account and updated atomically with each journal entry. This avoids expensive aggregation queries at read time.
  • Deterministic lock ordering: The BalanceUpdater sorts account IDs before acquiring FOR UPDATE locks to prevent deadlocks in concurrent transactions.
  • BigInt arithmetic: All monetary calculations use JavaScript BigInt to avoid floating-point precision issues. Amounts are stored as numeric(78,0) in PostgreSQL, supporting values up to 10^78.
  • Ceiling division for fees: Fee percentage calculations round UP (ceiling), ensuring the platform never undercharges.
  • Idempotency keys: All payment operations support idempotency keys. Terminal transactions are replayed; in-progress duplicates return 409 Conflict.
  • Fee resolution priority: Fee schedules are resolved by priority (highest wins), with merchant-specific schedules preferred over global ones. Schedules can be scoped by currency and transaction type.
  • Fee splitting: Fee schedules support splitting between platform and partner accounts via basis-point shares that must sum to 10,000.
  • Transactional outbox: Events are written in the same DB transaction as journal entries, ensuring no events are lost. A separate poller publishes to the external broker.
  • FX via suspense accounts: Cross-currency operations route through FX suspense accounts, preserving the same-currency invariant on every journal entry.
  • Hold TTL policies: Hierarchical hold TTL resolution (per-transaction > per-merchant+type+currency > global) with priority-based lookup.