cost

package module
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

Nexss Cost

github.com/nexssp/cost is a small, dependency-free Go library for fixed-point budget reservation and cost recording. The core module contains the local ledger and backend-neutral contracts only.

Modules

Module Use Dependencies
github.com/nexssp/cost Local in-process budgets and contracts Standard library only
github.com/nexssp/cost/adapters/redis Distributed Redis budgets go-redis/v9
github.com/nexssp/cost/adapters/postgres Transactional PostgreSQL budgets database/sql only
github.com/nexssp/cost/adapters/kernel Nexss Kernel action integration Nexss Kernel
github.com/nexssp/cost/testkit Adapter contract helper Core module

Examples are a separate module under examples/, so applications do not inherit example dependencies.

Core usage

ledger := cost.NewLedger(10_000_000, cost.USD)

reservation, err := ledger.Reserve(ctx, 1_000_000)
if err != nil {
	return err
}

if err := reservation.Commit(ctx, 800_000); err != nil {
	return err
}

err = ledger.Record(ctx, cost.Event{
	ID:         "request-123",
	Domain:     "ai",
	Operation:  "completion",
	CostMicros: 800_000,
	Currency:   cost.USD,
})
if err != nil {
	return err
}

Commit and Release return errors. A reservation is idempotent after its first successful terminal operation. Always handle the returned error; distributed backends can fail after the business operation completes.

Accounting distinction: on the local ledger, Commit updates SpentMicros and Record does not update it. Record is an audit operation only: it appends the event to the bounded audit ring. Record the event associated with a commit for traceability, but do not use Record as the spend counter.

Optional adapters

Install only the adapter you use:

go get github.com/nexssp/cost/adapters/redis
go get github.com/nexssp/cost/adapters/postgres
go get github.com/nexssp/cost/adapters/kernel
Redis
ledger := redisadapter.New(redisadapter.Config{
    Client: client,
    Prefix: "{cost}:checkout", // one Redis Cluster hash slot
    LimitMicros: 10_000_000,
    Currency: cost.USD,
})

Redis uses atomic Lua scripts, expiring leases, and lazy reclamation. Set a TTL longer than the longest expected operation. Redis is a coordination backend; keep durable audit records separately when required.

PostgreSQL

The PostgreSQL adapter accepts *sql.DB, not a concrete driver:

ledger := postgresadapter.New(postgresadapter.Config{
    DB: db,
    Scope: "checkout",
    LimitMicros: 10_000_000,
    Currency: cost.USD,
})
if err := ledger.EnsureSchema(ctx); err != nil { return err }

Applications choose pgx, lib/pq, pooling, tracing, and migrations. In production, run the exported postgres.Schema through your migration system rather than creating schema during request handling.

Implementing another backend

A custom backend implements only these contracts:

type Reserver interface {
    Reserve(context.Context, int64) (Reservation, error)
    Record(context.Context, Event) error
}

type Reservation interface {
    Commit(context.Context, int64) error
    Release(context.Context) error
}

Required guarantees:

  1. Reserve is atomic against the configured limit.
  2. Failed reservations consume no budget.
  3. Commit and Release are idempotent after a successful terminal operation.
  4. Context cancellation is respected before and during I/O.
  5. Expired or abandoned reservations cannot permanently consume budget.
  6. Backend failures are returned, never silently reported as success.
  7. Currency and scope are validated.
  8. Concurrent callers cannot exceed the limit.
  9. Recording is explicit and retry behavior is documented.

Use github.com/nexssp/cost/testkit as the starting point for adapter contract tests. Backend-specific behavior—leases, transactions, retries, consistency, and durability—must be documented by the adapter.

Documentation map

Start with docs/architecture.md for module boundaries, docs/semantics.md for accounting and failure semantics, and docs/extending.md for custom adapters. Runnable examples and infrastructure commands are in examples/README.md.

Semantics and performance

Amounts are integer micro-units: one unit equals 1,000,000 micros. The local ledger uses atomics for reservation accounting and a fixed 1024-entry ring for bounded audit memory. Its Entries snapshot allocates because it returns ownership-safe data; reservation hot paths do not use locks.

Network and database adapters necessarily include I/O, serialization, backend transactions, and failure handling. They are not zero-allocation paths. Measure local, Redis, and PostgreSQL paths separately under your workload rather than applying one performance claim to all backends.

Validation

go test ./...
go test -race ./...
go vet ./...
(cd adapters/redis && go test -race ./...)
(cd adapters/postgres && go test ./...)
(cd adapters/kernel && go test ./...)
(cd testkit && go test ./...)
(cd examples && go test ./... && go build ./...)

License

Apache License 2.0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	USD = Currency{'U', 'S', 'D'}
	EUR = Currency{'E', 'U', 'R'}
	GBP = Currency{'G', 'B', 'P'}
	PLN = Currency{'P', 'L', 'N'}
	TOK = Currency{'T', 'O', 'K'}
)
View Source
var (
	ErrNilContext          = errors.New("cost: nil context")
	ErrBudgetExceeded      = errors.New("cost: budget exceeded")
	ErrValidation          = errors.New("cost: validation error")
	ErrInvalidEstimate     = errors.New("cost: invalid estimate")
	ErrInvalidCost         = errors.New("cost: invalid cost")
	ErrReservationNotFound = errors.New("cost: reservation not found or expired")
)

Functions

This section is empty.

Types

type CostReporter

type CostReporter interface {
	CostMicros() int64
}

CostReporter lets an action result report actual usage.

type Currency

type Currency [3]byte

Currency is a 3-byte ISO-4217 code (USD, EUR) or custom credit code (TOK). Packed into 3 bytes for register-passing and zero heap allocations.

func (Currency) MarshalJSON

func (c Currency) MarshalJSON() ([]byte, error)

func (Currency) MarshalText

func (c Currency) MarshalText() ([]byte, error)

func (Currency) String

func (c Currency) String() string

func (*Currency) UnmarshalJSON

func (c *Currency) UnmarshalJSON(b []byte) error

func (*Currency) UnmarshalText

func (c *Currency) UnmarshalText(b []byte) error

type Event

type Event struct {
	ID         string    `json:"id,omitempty"`
	Domain     string    `json:"domain"`
	Operation  string    `json:"operation"`
	CostMicros int64     `json:"cost_micros"`
	Currency   Currency  `json:"currency"`
	Timestamp  time.Time `json:"timestamp,omitempty"`
}

Event is an immutable accounting/audit record. ID should be stable across retries.

type Ledger

type Ledger struct {
	// contains filtered or unexported fields
}

Ledger is the bounded-memory, in-process implementation. Reserve/commit/ release use atomics; only audit snapshots take a short read lock.

func NewLedger

func NewLedger(limitMicros int64, currency Currency) *Ledger

func (*Ledger) Currency

func (l *Ledger) Currency() Currency

func (*Ledger) Entries

func (l *Ledger) Entries() []Event

func (*Ledger) LimitMicros

func (l *Ledger) LimitMicros() int64

func (*Ledger) Record

func (l *Ledger) Record(ctx context.Context, event Event) error

func (*Ledger) Reserve

func (l *Ledger) Reserve(ctx context.Context, estimateMicros int64) (Reservation, error)

func (*Ledger) SpentMicros

func (l *Ledger) SpentMicros() int64

func (*Ledger) UsedMicros

func (l *Ledger) UsedMicros() int64

type Micro

type Micro int64

Micro represents fixed-point 1/1,000,000th of a currency unit ($0.000001).

func ToMicro

func ToMicro(amount float64) Micro

func (Micro) Float64

func (m Micro) Float64() float64

type Reservation

type Reservation interface {
	Commit(ctx context.Context, actualMicros int64) error
	Release(ctx context.Context) error
}

Reservation is a terminal, idempotent lease. Terminal operations return backend failures; callers must not claim success when an error is returned.

type Reserver

type Reserver interface {
	Reserve(ctx context.Context, estimateMicros int64) (Reservation, error)
	Record(ctx context.Context, event Event) error
}

Reserver is the backend-neutral budget contract.

type ValidationError

type ValidationError struct {
	Field string
	Value any
}

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

Directories

Path Synopsis
adapters
kernel module
postgres module
redis module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL