Kolo.Ledger 0.0.8

dotnet add package Kolo.Ledger --version 0.0.8
                    
NuGet\Install-Package Kolo.Ledger -Version 0.0.8
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Kolo.Ledger" Version="0.0.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Kolo.Ledger" Version="0.0.8" />
                    
Directory.Packages.props
<PackageReference Include="Kolo.Ledger" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Kolo.Ledger --version 0.0.8
                    
#r "nuget: Kolo.Ledger, 0.0.8"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Kolo.Ledger@0.0.8
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Kolo.Ledger&version=0.0.8
                    
Install as a Cake Addin
#tool nuget:?package=Kolo.Ledger&version=0.0.8
                    
Install as a Cake Tool

Kolo.Ledger

kolo (Yoruba) — piggy box / savings box

Production-grade double-entry ledger library with HMAC-SHA256 hash-chain integrity, period-end closing, multi-book support, and a fully typed audit trail. Built for .NET 10 on EF Core + PostgreSQL.

Package Version
Kolo.Ledger NuGet

Features

  • Double-entry accounting — automatic debit/credit balance tracking with contra-account awareness
  • HMAC-SHA256 hash chain — every entry is cryptographically linked to its predecessor, providing tamper-evidence and non-repudiation
  • Append-only audit trailJournalEntry and JournalLine are immutable after creation; SaveChangesAsync throws ImmutableRecordException on any modify or delete
  • Transaction reversal — creates fully offsetting entries (never deletes originals), preserving audit history
  • Multi-book (ledger) architecture — run primary, supplemental, budget, tax, and statutory books side-by-side with cross-book reconciliation
  • Period management — lock/unlock accounting periods to prevent post-close adjustments
  • Period-end closing — automated closing entries that zero out income statement accounts into retained earnings
  • Keyset (cursor-based) pagination — all list queries use MR.EntityFrameworkCore.KeysetPagination; no Skip/Take anywhere
  • Hash-chain integrity verification — full ledger scan that validates every entry's hash, sequence, and normal balance
  • Multi-tenancyITenantProvider-based data isolation per organisation
  • Role-based access controlIFinanceAuthorizationService with granular permissions (finance.journal.post, finance.period.lock, etc.)
  • Export — built-in JSON and CSV export for audit and reporting
  • Export service — built-in JSON/CSV export for journal entries, trial balance, and GL
  • Access logging — all sensitive operations are logged to FinanceAuditEntry for SOX compliance
  • EF Core migrations — ready-to-run PostgreSQL schema under the finance schema

Quickstart

1. Install

dotnet add package Kolo.Ledger

2. Configure services

builder.Services.AddLedger(
    connectionString: "Host=localhost;Port=7203;Database=finance");

3. Set the HMAC signing key

The hash chain requires a symmetric key (minimum 32 bytes, Base64-encoded). Pass it via LedgerOptions:

builder.Services.AddLedger(
    "Host=localhost;Port=7203;Database=finance",
    options =>
    {
        options.HmacSecretKey = configuration["KoloLedger:HmacSecretKey"];
    });

You supply the key from your own configuration or secure store (KMS, vault, etc.). The key persists across restarts — it is never generated at startup.

4. Apply migrations

dotnet ef database update --project Kolo.Ledger

Or apply at startup:

await using var scope = app.Services.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<FinanceDbContext>();
await db.Database.MigrateAsync();

5. Seed the chart of accounts

var db = scope.ServiceProvider.GetRequiredService<FinanceDbContext>();
var accountProvider = scope.ServiceProvider.GetRequiredService<ISystemAccountProvider>();
await ChartOfAccountsSeeder.SeedAsync(db, accountProvider);

6. Inject into your own services

builder.Services.AddScoped<InvoiceService>();

public class InvoiceService
{
    private readonly ILedgerService _ledger;

    public InvoiceService(ILedgerService ledger) => _ledger = ledger;

    public async Task RecordPayment(Guid cashAccountId, Guid revenueAccountId, Guid bookId)
    {
        var entry = await _ledger.Journal.RecordEntryAsync(new JournalEntryRequest(
            Reference: "INV-001",
            Description: "Invoice payment received",
            BookId: bookId,
            Lines:
            [
                new(cashAccountId, 1000m, LedgerEntryType.Debit),
                new(revenueAccountId, 1000m, LedgerEntryType.Credit),
            ]));
    }
}

You can also inject individual service interfaces instead of the full facade:

public class BalanceChecker
{
    private readonly IBalanceService _balance;

    public BalanceChecker(IBalanceService balance) => _balance = balance;

    public async Task<decimal> GetCashBalance(Guid cashAccountId)
        => await _balance.GetBalanceAsync(cashAccountId);
}

Full project setup

A complete example from dotnet new to working ledger:

1. Create a new project

dotnet new webapi -n MyFinanceApp
cd MyFinanceApp
dotnet add package Kolo.Ledger

2. Configure Program.cs

using Kolo.Ledger.Extensions;
using Kolo.Ledger.Seeding;
using Kolo.Ledger.Entities;

var builder = WebApplication.CreateBuilder(args);

// Register ledger services
builder.Services.AddLedger(builder.Configuration.GetConnectionString("FinanceDb")!);

// Register your own services
builder.Services.AddScoped<MyFinanceService>();

// Add controllers
builder.Services.AddControllers();

var app = builder.Build();

// Migrate + seed at startup
await using var scope = app.Services.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<Kolo.Ledger.Data.FinanceDbContext>();
await db.Database.MigrateAsync();

var accountProvider = scope.ServiceProvider.GetRequiredService<Kolo.Ledger.Seeding.ISystemAccountProvider>();
await ChartOfAccountsSeeder.SeedAsync(db, accountProvider);
var book = await BookSeeder.SeedDefaultBookAsync(db);

app.MapControllers();
app.Run();

3. Use in a controller

[ApiController]
[Route("invoices")]
public class InvoicesController : ControllerBase
{
    private readonly ILedgerService _ledger;

    public InvoicesController(ILedgerService ledger) => _ledger = ledger;

    [HttpPost]
    public async Task<IActionResult> Post(JournalEntryRequest request)
    {
        var entry = await _ledger.Journal.RecordEntryAsync(request);
        return Ok(entry);
    }

    [HttpGet("balance/{accountId}")]
    public async Task<IActionResult> GetBalance(Guid accountId)
    {
        var balance = await _ledger.Balance.GetBalanceAsync(accountId);
        return Ok(balance);
    }
}

4. Add the connection string to appsettings.json

{
  "ConnectionStrings": {
    "FinanceDb": "Host=localhost;Port=7203;Database=finance"
  }
}

5. Run

dotnet run

Configuration

Connection string

// Simplest form
services.AddLedger("Host=localhost;Port=7203;Database=finance");

// With schema override
services.AddLedger("Host=localhost;Port=7203;Database=finance", schemaName: "my_finance");

// With full options
services.AddLedger(options =>
{
    options.ConnectionString = "Host=localhost;Port=7203;Database=finance";
    options.SchemaName = "finance";
    options.MaxPageSize = 200;
    options.RetryMaxAttempts = 3;
    options.SeedDefaultBook = true;
});

LedgerOptions reference

Option Default Description
SchemaName "finance" PostgreSQL schema for all ledger tables
MaxPageSize 100 Maximum items per keyset-paginated page
HmacSecretKey null Base64-encoded HMAC-SHA256 signing key (minimum 32 bytes when decoded)
HmacKeyMinimumLength 32 Minimum bytes for the HMAC signing key
RetryMaxAttempts 5 Retry attempts for sequence conflict resolution
RetryBaseDelayMs 100 Base delay (ms) for exponential backoff
RetryMaxDelayMs 5000 Maximum delay (ms) between retries
LockTimeoutMs 5000 Lock timeout (ms) for period operations
IncludeErrorDetails true Include detailed error messages in exceptions
SeedDefaultBook true Auto-seed a default primary book on first use

HMAC key configuration

Pass the key (minimum 32 bytes, Base64-encoded) through LedgerOptions.HmacSecretKey:

services.AddLedger("Host=localhost;Port=7203;Database=finance", options =>
{
    options.HmacSecretKey = configuration["KoloLedger:HmacSecretKey"];
});

You supply the key from your own configuration or secure store (Azure Key Vault, AWS KMS, HashiCorp Vault, etc.). Or provide a custom IHmacKeyProvider implementation:

services.AddSingleton<IHmacKeyProvider>(sp => new MyVaultHmacKeyProvider());

Multi-tenancy

Implement ITenantProvider to scope data by organisation:

services.AddScoped<ITenantProvider, MyTenantProvider>();

public class MyTenantProvider : ITenantProvider
{
    private readonly IHttpContextAccessor _http;
    public MyTenantProvider(IHttpContextAccessor http) => _http = http;

    public Guid? GetOrganisationId()
    {
        var claim = _http.HttpContext?.User.FindFirst("org_id");
        return claim is not null ? Guid.Parse(claim.Value) : null;
    }
}

In single-tenant mode, NullTenantProvider is used (no filtering).


Core Concepts

Double-entry accounting

Every journal entry has at least two lines: one debit and one credit. Total debits must equal total credits. The library enforces this at the domain level and will throw UnbalancedTransactionException if violated.

Account types & normal balances

Account Type Normal Balance Contra Normal Classification
Asset Debit Credit (contra-asset) Balance Sheet
Liability Credit Debit (contra-liability) Balance Sheet
Equity Credit Debit (contra-equity) Balance Sheet
Revenue Credit Debit (contra-revenue) Income Statement
Expense Debit Credit (contra-expense) Income Statement

Use AccountExtensions.CalculateBalance() to determine an account's net position:

var balance = AccountExtensions.CalculateBalance(account.Type, totalDebits, totalCredits);

Books (ledgers)

A Book represents a logical ledger. You can have multiple books for different purposes:

  • Primary — the main general ledger
  • Supplemental — sub-ledgers for specific departments or entities
  • Budget — budget vs actual tracking
  • Tax — tax-basis accounting
  • Statutory — regulatory reporting

Books can be reconciled against each other via IReconciliationService:

var report = await reconciliationService.ReconcileAsync(
    sourceBookId: primaryBookId,
    targetBookId: taxBookId);
// report.IsBalanced, report.Differences, report.TotalAbsoluteDifference

The hash chain

Every JournalEntry stores:

  • SequenceNumber — monotonically increasing (unique per book)
  • PreviousHash — the Hash of the preceding entry in the same book
  • HashHMAC-SHA256(PreviousHash + EntryData + LineData, key)
  • SignatureVersion — algorithm version for future-proofing

The genesis entry uses "GENESIS" as its PreviousHash. The chain is verified by IVerifyIntegrityCommand, which checks:

  1. No sequence number gaps
  2. Every PreviousHash matches the predecessor's Hash
  3. Every entry's Hash recomputes correctly
  4. Every line's normal balance is correct

Immutability

JournalEntry and JournalLine use init-only setters and private constructors with static factory methods. The FinanceDbContext.SaveChangesAsync override explicitly prevents any modification or deletion of these entity types by throwing ImmutableRecordException.


Usage

Recording entries

public class PurchasingService
{
    private readonly ILedgerService _ledger;

    public PurchasingService(ILedgerService ledger) => _ledger = ledger;

    public async Task RecordPurchase(Guid suppliesExpenseId, Guid cashAccountId, Guid bookId)
    {
        var entry = await _ledger.Journal.RecordEntryAsync(new JournalEntryRequest(
            Reference: "PUR-042",
            Description: "Office supplies purchase",
            BookId: bookId,
            EntryDate: DateTime.UtcNow,
            Source: "purchasing",
            Lines:
            [
                new(suppliesExpenseId, 250.00m, LedgerEntryType.Debit),
                new(cashAccountId, 250.00m, LedgerEntryType.Credit),
            ],
            CreatedByUserId: "alice",
            CreatedByUserName: "Alice Johnson",
            CorrelationId: "req-abc-123",
            RequestIp: "192.168.1.1",
            UserAgent: "ERP/v2.1"));
    }
}

Reversing entries

Reversal creates a new entry with opposite debits/credits linked to the original:

public class RefundService
{
    private readonly ILedgerService _ledger;

    public RefundService(ILedgerService ledger) => _ledger = ledger;

    public async Task RefundOrder(Guid originalEntryId)
    {
        var reversal = await _ledger.Journal.ReverseEntryAsync(
            entryId: originalEntryId,
            reason: "Customer refund — order cancelled",
            createdByUserId: "bob");
    }
}

The reversal entry has IsReversal = true, ReversedEntryId pointing to the original, and is timestamped in the audit log.

Trial balance

public class ReportingService
{
    private readonly ILedgerService _ledger;

    public ReportingService(ILedgerService ledger) => _ledger = ledger;

    public async Task PrintTrialBalance(Guid bookId)
    {
        var trialBalance = await _ledger.Reporting.GetTrialBalanceAsync(
            year: 2026, month: 7, bookId: bookId);

        Console.WriteLine($"Balanced: {trialBalance.IsBalanced}");
        Console.WriteLine($"Total Debits: {trialBalance.TotalDebits}");
        Console.WriteLine($"Total Credits: {trialBalance.TotalCredits}");

        foreach (var entry in trialBalance.Entries)
            Console.WriteLine($"{entry.AccountCode} {entry.AccountName}: {entry.Balance}");
    }
}

General ledger

public class GlReportService
{
    private readonly ILedgerService _ledger;

    public GlReportService(ILedgerService ledger) => _ledger = ledger;

    public async Task PrintGl(Guid cashAccountId, Guid bookId)
    {
        var gl = await _ledger.Reporting.GetGeneralLedgerAsync(new GeneralLedgerRequest(
            AccountId: cashAccountId,
            DateFrom: new DateTime(2026, 1, 1),
            DateTo: new DateTime(2026, 12, 31),
            BookId: bookId));

        Console.WriteLine($"Opening: {gl.OpeningBalance}, Closing: {gl.ClosingBalance}");
    }
}

Listing entries (keyset pagination)

public class EntryLister
{
    private readonly ILedgerService _ledger;

    public EntryLister(ILedgerService ledger) => _ledger = ledger;

    public async Task ListEntries(Guid bookId)
    {
        var result = await _ledger.Journal.ListEntriesAsync(new JournalEntryFilter
        {
            BookId = bookId,
            PageSize = 50,
            Direction = KeysetPaginationDirection.Forward,
        });

        foreach (var item in result.Items) { /* ... */ }

        // Navigate forward
        if (result.HasNext)
        {
            var nextPage = await _ledger.Journal.ListEntriesAsync(new JournalEntryFilter
            {
                BookId = bookId,
                ReferenceCursor = result.NextReference,
                Direction = KeysetPaginationDirection.Forward,
                PageSize = 50,
            });
        }
    }
}

Period management

public class PeriodManager
{
    private readonly ILedgerService _ledger;

    public PeriodManager(ILedgerService ledger) => _ledger = ledger;

    public async Task ManagePeriods(Guid bookId)
    {
        // Lock a period to prevent new entries
        await _ledger.Periods.LockPeriodAsync(year: 2026, month: 6, lockedByUserId: "admin");

        // Unlock (requires finance.period.lock permission)
        await _ledger.Periods.UnlockPeriodAsync(year: 2026, month: 6, unlockedByUserId: "admin");

        // Close a period — zeroes income statement accounts to retained earnings
        await _ledger.Periods.ClosePeriodAsync(year: 2026, month: 6, bookId: bookId,
            createdByUserId: "admin");
    }
}

Integrity verification

public class AuditService
{
    private readonly ILedgerService _ledger;

    public AuditService(ILedgerService ledger) => _ledger = ledger;

    public async Task VerifyLedgerIntegrity()
    {
        var integrity = await _ledger.Audit.VerifyIntegrityAsync();
        Console.WriteLine($"Valid: {integrity.IsValid}");
        Console.WriteLine($"Entries checked: {integrity.TotalEntriesChecked}");
        Console.WriteLine($"Errors: {integrity.ErrorCount}");

        foreach (var error in integrity.Errors)
            Console.WriteLine($"[{error.ErrorType}] Entry {error.SequenceNumber}: {error.Message}");
    }
}

Export

public class ExportService
{
    private readonly IExportService _export;

    public ExportService(IExportService export) => _export = export;

    public async Task ExportData(List<JournalEntryDto> entries, TrialBalanceReport trialBalance)
    {
        string json = await _export.ExportToJsonAsync(entries);
        string csv = await _export.ExportToCsvAsync(trialBalance.Entries);
    }
}

Typed metadata (accounts, journal lines, journal entries)

Every Account, JournalLine, and JournalEntry supports strongly-typed metadata via extension methods. Metadata is stored as a single JSONB column per entity — one POCO per entity. If you need multiple data shapes, compose a single DTO:

public record WalletInfo(string WalletType, string Status);

AccountMetadataJson (JSONB column, string?):

account.SetMetadata(new WalletInfo("main", "active"));

var meta = account.GetMetadata<WalletInfo>();
// meta?.WalletType == "main"

bool has = account.HasMetadata<WalletInfo>();   // true
account.RemoveMetadata<WalletInfo>();

JournalLine — same pattern as Account:

line.SetMetadata(new WalletInfo("main", "active"));

var meta = line.GetMetadata<WalletInfo>();

JournalEntry — serializes/deserializes via the existing Dictionary<string, object?> property. Free-form dictionary access and typed access operate on the same data:

entry.SetMetadata(new WalletInfo("main", "active"));

// Typed access:
var meta = entry.GetMetadata<WalletInfo>();

// Free-form legacy access still works:
entry.Metadata["walletType"].ToString();   // "main"

// They share the same storage — SetMetadata replaces the entire dictionary.

Design notes:

  • One type per entity — compose a DTO if you need multiple data groups
  • If you rename a POCO type, existing JSONB data will silently fail to deserialize
  • For JournalEntry, SetMetadata<T>() replaces the entire dictionary; use either the typed API or free-form dict access on a given entry
  • Journal lines are immutable after save — set metadata before the first SaveChangesAsync
  • Metadata is not included in the hash chain (it is not part of the accounting integrity check)
public class CashBalanceService
{
    private readonly ILedgerService _ledger;

    public CashBalanceService(ILedgerService ledger) => _ledger = ledger;

    public async Task<decimal> GetCashBalance(Guid cashAccountId)
        => await _ledger.Balance.GetBalanceAsync(accountId: cashAccountId);
}

Authorization & Permissions

The library includes a built-in permission system accessed via IFinanceAuthorizationService:

Permission Constant Operation
finance.journal.post FinancePermissions.JournalPost Record journal entries
finance.journal.reverse FinancePermissions.JournalReverse Reverse entries
finance.period.lock FinancePermissions.PeriodLock Lock/unlock periods
finance.period.close FinancePermissions.PeriodClose Close accounting periods
finance.audit.view FinancePermissions.AuditView View audit logs and integrity reports
await authorizationService.AssertPermissionAsync(FinancePermissions.JournalPost, ct);
string userId = authorizationService.CurrentUserId;

Implement IFinanceAuthorizationService or override ICurrentUser for custom authorization logic. The default NullCurrentUser grants all permissions.


Exceptions

Exception Cause
UnbalancedTransactionException Debits do not equal credits in a journal entry
ImmutableRecordException Attempted to modify or delete a JournalEntry or JournalLine
PeriodLockedException Attempted to post to a locked accounting period
SequenceConflictException Concurrent sequence number allocation conflict (auto-retried)
ContraNormalBalanceException Zero or negative balance on a normal-balance account that disallows negatives
InsufficientBalanceException Insufficient funds for a debit on a balance-based account
NegativeBalanceForbiddenException Balance would go negative on an account with AllowNegativeBalance = false
SystemAccountConstraintException System account assigned to an organisation or vice versa

Architecture

Kolo.Ledger/
├── Abstractions/           — Public interfaces for all injectable services
│   ├── IRecordEntryCommand
│   ├── IReverseEntryCommand
│   ├── IGetBalanceQuery
│   ├── IGetTrialBalanceQuery
│   ├── IClosePeriodCommand
│   ├── IVerifyIntegrityCommand
│   ├── IExportService
│   ├── IReconciliationService
│   ├── ITenantProvider
│   ├── IFinanceAuthorizationService
│   └── ICurrentUser
│
├── Configuration/          — EF Core IEntityTypeConfiguration<T> classes
├── Core/                   — FinanceConstants, FinancePermissions, LedgerOptions
├── DTOs/                   — Request/response types
├── Entities/               — Domain model with init-only properties
├── Exceptions/             — Typed exception hierarchy
├── Extensions/             — DI registration (AddLedger)
├── Features/               — Feature-first commands & queries
│   ├── Accounts/           — GetBalance, GetAccountBalance
│   ├── Audit/              — VerifyIntegrity
│   ├── Books/              — CreateBook, ListBooks, ReconcileBooks
│   ├── Closing/            — ClosePeriod
│   ├── JournalEntries/     — RecordEntry, ReverseEntry, ValidateEntry
│   ├── Periods/            — LockPeriod, UnlockPeriod, EnsurePeriodExists
│   └── Reporting/          — TrialBalance, GeneralLedger, ListJournalEntries
├── Mapper/                 — Shared mapping extensions
├── Migrations/             — EF Core PostgreSQL migrations
├── Seeding/                — ChartOfAccountsSeeder, BookSeeder
└── Services/               — AccessLogger, HmacKeyProvider, ExportService

Design principles

  • Feature-first folders — each feature (Accounts, JournalEntries, Periods, etc.) is self-contained with its own Commands/, Queries/, and Mapper/ sub-folders
  • Action classes — every use case is a single class with one public ExecuteAsync(...) method; all EF queries live in private methods
  • No unit of work wrapper — commands interact with FinanceDbContext directly; SaveChangesAsync is called explicitly
  • Keyset pagination everywhere — all list queries use cursor-based pagination via MR.EntityFrameworkCore.KeysetPagination; never Skip/Take
  • Explicit DI registration — no Scrutor or assembly scanning; all services registered individually in ServiceCollectionExtensions

Compliance & Audit

Framework Focus Status
GAAP (ASC 205-250) Revenue recognition, balance sheet presentation 85/100
IFRS (IAS 1, 21, 37) Financial statement presentation, provisions 78/100
SOX 404 (ICFR) Internal controls over financial reporting 78/100
ACID Transactional integrity, isolation, durability 82/100

The library provides:

  • Append-only journal tables — no deletions or silent edits
  • HMAC-SHA256 hash chain — tamper evidence with cryptographic verification
  • Full access logging — every sensitive operation recorded in FinanceAuditEntry
  • RowVersion concurrency — optimistic locking on all entities
  • Transaction reversal — audit-preserving offset entries (never deletes originals)

Migrations & Deployment

The package ships with EF Core migrations for PostgreSQL under the finance schema:

# Generate a new migration
dotnet ef migrations add AddNewFeature --project Kolo.Ledger

# Apply to database
dotnet ef database update --project Kolo.Ledger

# Apply programmatically
var db = scope.ServiceProvider.GetRequiredService<FinanceDbContext>();
await db.Database.MigrateAsync();

Operational requirements

For production PostgreSQL deployment:

  1. fsync=on — committed writes survive power loss
  2. synchronous_commit=on — COMMIT returns after WAL flush
  3. WAL archiving to S3/GCS — point-in-time recovery capability
  4. Hourly backups of the finance schema
  5. Quarterly restore testing
  6. RTO < 1 hour, RPO < 1 minute

Seeding

The library includes two seeders for initial setup:

// Seeds system accounts (Cash, Accounts Receivable, Revenue, etc.)
var db = scope.ServiceProvider.GetRequiredService<FinanceDbContext>();
var accountProvider = scope.ServiceProvider.GetRequiredService<ISystemAccountProvider>();
await ChartOfAccountsSeeder.SeedAsync(db, accountProvider);

// Seeds default books (Primary, Supplemental, Tax)
await BookSeeder.SeedDefaultBookAsync(db);

System accounts are defined in SystemAccountDefinition records and are identifiable by IsSystem = true. They don't belong to any organisation.


Development

Build

dotnet build Kolo.Ledger/Kolo.Ledger.csproj

Run tests

dotnet test Kolo.Ledger/tests/Kolo.Ledger.Tests

Generate a migration

export FINANCE_DB_CONNECTION="Host=localhost;Database=finance"
dotnet ef migrations add MigrationName --project Kolo.Ledger

Pack for NuGet

dotnet pack Kolo.Ledger/Kolo.Ledger.csproj -c Release -o ./nupkg

Publish to NuGet

dotnet nuget push ./nupkg/Kolo.Ledger.0.1.0.nupkg \
    --api-key $NUGET_API_KEY \
    --source https://api.nuget.org/v3/index.json

Package Contents

When installed, Kolo.Ledger provides:

  • Kolo.Ledger.dll — the compiled library
  • Kolo.Ledger.xml — IntelliSense XML documentation
  • Kolo.Ledger.pdb — symbols (via snupkg) for debugging
  • README.md — this documentation (visible on nuget.org)

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.8 0 7/23/2026
0.0.7 0 7/23/2026
0.0.6 0 7/23/2026
0.0.5 0 7/23/2026
0.0.4 0 7/22/2026
0.0.3 0 7/22/2026
0.0.2 0 7/22/2026
0.0.1 0 7/22/2026