go-accumulator

Go client for pg_accumulator — high-performance, type-safe adapter for accumulation registers.

Designed to work seamlessly with sqlc, pgx/v5, and any Go ORM (GORM, ent, sqlx).


Проблема

pg_accumulator для Go потребує: - Виклик SQL-функцій (register_post, register_unpost) через parametrized queries - Серіалізацію map[string]any → JSONB без SQL injection - Type-safe читання результатів balance() / turnover() у Go-структури - Сумісності з транзакційними інтерфейсами (pgx.Tx, sql.Tx, gorm.DB)

Hand-written raw queries — повторюваний, ненадійний код без типів і централізованої обробки помилок.


Рішення

go-accumulator — легкий Go-пакет, який:

  1. Надає єдиний інтерфейс для всіх операцій pg_accumulator
  2. Підтримує генерацію коду через sqlc — SQL-файли включені у пакет
  3. Сумісний з будь-яким PostgreSQL-драйвером через DBTX інтерфейс
  4. Не нав'язує ORM — workс equally well з pgx, database/sql, GORM

Архітектура

┌─────────────────────────────────────────────────────────────────┐
│                        Your Application                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   GORM / ent / sqlx        go-accumulator                      │
│   (бізнес-таблиці:         ├── Register[D, R]                  │
│    users, orders)          │    ├── Post(ctx, movement)        │
│                            │    ├── Unpost(ctx, recorder)      │
│                            │    ├── Repost(ctx, ...)           │
│                            │    ├── Balance(ctx, dims)         │
│                            │    ├── Turnover(ctx, dims, ...)   │
│                            │    └── Movements(ctx, ...)        │
│                            │                                   │
│                            ├── AccumulatorClient               │
│                            │    ├── Use[D,R](def)              │
│                            │    └── WithTx(tx)                 │
│                            │                                   │
│                            └── sqlc queries (generated)        │
│                                 ├── PostMovement()             │
│                                 ├── UnpostMovement()           │
│                                 ├── GetBalance()               │
│                                 └── GetTurnover()              │
├─────────────────────────────────────────────────────────────────┤
│                   DBTX interface                                │
│   pgx.Pool │ pgx.Tx │ *sql.DB │ *sql.Tx │ GORM tx             │
├─────────────────────────────────────────────────────────────────┤
│              PostgreSQL + pg_accumulator                        │
│  ┌───────────┐  ┌──────────┐  ┌──────────────┐                │
│  │ movements │  │  totals  │  │balance_cache │                │
│  └───────────┘  └──────────┘  └──────────────┘                │
└─────────────────────────────────────────────────────────────────┘

Ключовий принцип: DBTX Interface

Вся логіка будується на одному інтерфейсі — пакет не залежить від конкретного драйвера:

// internal/db/db.go — генерується sqlc, адаптується вручну
type DBTX interface {
    ExecContext(ctx context.Context, q string, args ...any) (sql.Result, error)
    QueryContext(ctx context.Context, q string, args ...any) (*sql.Rows, error)
    QueryRowContext(ctx context.Context, q string, args ...any) *sql.Row
}

// Підтримуються:
// *sql.DB, *sql.Tx          — database/sql
// *pgxpool.Pool, pgx.Tx    — pgx/v5 через pgxpool
// *gorm.DB (через db.DB()) — GORM
// *sqlx.DB, *sqlx.Tx       — sqlx

Завдяки цьому один і той самий AccumulatorClient працює у будь-якому контексті.


API Design

1. Визначення реєстру

import "github.com/pg-accumulator/go-accumulator/v2"

// RegisterDef описує схему реєстру в коді Go.
// Відповідає параметрам register_create().
var Inventory = accum.RegisterDef{
    Name:         "inventory",
    Kind:         accum.Balance,
    TotalsPeriod: accum.Day,
    PartitionBy:  accum.Month,
    HighWrite:    false,
    Dimensions: accum.Dims{
        "warehouse": accum.Int,
        "product":   accum.Int,
        "lot":       accum.Text,
    },
    Resources: accum.Res{
        "quantity": accum.Numeric,
        "amount":   accum.Numeric,
    },
}

// For kind=turnover:
var Sales = accum.RegisterDef{
    Name: "sales",
    Kind: accum.Turnover,
    Dimensions: accum.Dims{
        "customer": accum.Int,
        "product":  accum.Int,
    },
    Resources: accum.Res{
        "quantity": accum.Numeric,
        "revenue":  accum.Numeric,
    },
}

2. Typed-structs для конкретного реєстру

// Визначаються у вашому проекті (або генеруються CLI):
type InventoryDims struct {
    Warehouse int  `json:"warehouse"`
    Product   int  `json:"product"`
    Lot       *string `json:"lot,omitempty"`
}

type InventoryResources struct {
    Quantity decimal.Decimal `json:"quantity"`
    Amount   decimal.Decimal `json:"amount"`
}

type InventoryMovement struct {
    Recorder string    `json:"recorder"`
    Period   time.Time `json:"period"`
    InventoryDims
    InventoryResources
}

3. AccumulatorClient

import (
    "context"
    accum "github.com/pg-accumulator/go-accumulator/v2"
)

// Ініціалізація
pool, _ := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
client    := accum.New(pool, accum.Options{Schema: "accum"})

// Отримати клієнт для конкретного реєстру (generics)
inventory := accum.Use[InventoryDims, InventoryResources](client, Inventory)

4. Write API

ctx := context.Background()

// --- POST — один рух ---
count, err := inventory.Post(ctx, InventoryMovement{
    Recorder: "purchase:7001",
    Period:   time.Now(),
    InventoryDims{Warehouse: 1, Product: 42, Lot: ptr("LOT-A")},
    InventoryResources{Quantity: d("100"), Amount: d("5000.00")},
})

// --- POST BATCH — масив рухів (один SQL запит) ---
count, err := inventory.PostBatch(ctx, []InventoryMovement{
    {Recorder: "purchase:7001", Period: time.Now(), ...},
    {Recorder: "purchase:7001", Period: time.Now(), ...},
})

// --- UNPOST ---
err := inventory.Unpost(ctx, "purchase:7001")

// --- REPOST ---
err := inventory.Repost(ctx, "purchase:7001", []InventoryMovement{
    {Recorder: "purchase:7001", Period: time.Now(), ...},
})

5. Read API

// --- BALANCE (поточний) ---
bal, err := inventory.Balance(ctx, InventoryDims{
    Warehouse: 1,
    Product:   42,
})
// bal.Quantity = 100, bal.Amount = 5000.00

// --- BALANCE (на дату) ---
atDate := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
bal, err := inventory.BalanceAt(ctx, InventoryDims{Warehouse: 1}, atDate)

// --- BALANCE (частковий filter — всі продукти на складі 1) ---
// передати тільки warehouse, product = nil
bal, err := inventory.Balance(ctx, InventoryDims{Warehouse: 1})

// --- TURNOVER ---
turn, err := inventory.Turnover(ctx,
    InventoryDims{Warehouse: 1},
    accum.TurnoverOptions{
        DateFrom: ptr(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)),
        DateTo:   ptr(time.Date(2026, 3, 31, 23, 59, 59, 0, time.UTC)),
    },
)
// turn.Quantity, turn.Amount — агрегат за квартал

// --- MOVEMENTS ---
moves, err := inventory.Movements(ctx,
    InventoryDims{Warehouse: 1, Product: 42},
    accum.MovementsOptions{
        DateFrom: ptr(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)),
        Limit:    50,
        Offset:   0,
        OrderBy:  accum.OrderByPeriod,
        Order:    accum.OrderDesc,
    },
)

6. DDL Management

// Створення реєстру в БД
err := client.CreateRegister(ctx, Inventory)

// Зміна реєстру
err := client.AlterRegister(ctx, "inventory", accum.AlterOptions{
    AddDimensions: accum.Dims{"color": accum.Text},
    AddResources:  accum.Res{"weight": accum.Numeric},
})

// Видалення
err := client.DropRegister(ctx, "inventory")

// Список реєстрів
registers, err := client.ListRegisters(ctx)

// Детальна інформація
info, err := client.RegisterInfo(ctx, "inventory")

7. Transactions — сумісність з усіма ORM

// --- З database/sql ---
tx, _ := db.BeginTx(ctx, nil)
txClient := client.WithTx(tx)
txInv := accum.Use[InventoryDims, InventoryResources](txClient, Inventory)
err = txInv.Post(ctx, ...)
tx.Commit()

// --- З pgx ---
tx, _ := pool.Begin(ctx)
txClient := client.WithTx(pgxstdlib.OpenDBFromPool(tx))
// або використати pgx-adapter
txClient := client.WithPgxTx(tx)
err = accum.Use[InventoryDims, InventoryResources](txClient, Inventory).Post(ctx, ...)
tx.Commit(ctx)

// --- З GORM ---
db.Transaction(func(tx *gorm.DB) error {
    sqlDB, _ := tx.DB()
    txAccum := client.WithTx(sqlDB)
    txInv := accum.Use[InventoryDims, InventoryResources](txAccum, Inventory)
    return txInv.Post(ctx, InventoryMovement{...})
})

sqlc Integration

go-accumulator надає готові .sql файли для sqlc генерації.

sqlc.yaml

# sqlc.yaml
version: "2"
sql:
  - engine: "postgresql"
    queries:
      - "queries/app.sql"
      - "${GOPATH}/pkg/mod/github.com/pg-accumulator/go-accumulator/v2@latest/sqlc/queries.sql"
    schema:
      - "schema/"
    gen:
      go:
        out: "internal/db"
        package: "db"
        sql_package: "pgx/v5"
        emit_json_tags: true
        emit_interface: true
        emit_exact_table_names: false

Вбудовані sqlc queries (sqlc/queries.sql)

Пакет включає готові SQL-шаблони з іменованими параметрами:

-- sqlc/queries.sql

-- name: RegisterPost :one
-- Записати один або батч рухів у реєстр
SELECT accum.register_post(@register::text, @data::jsonb) AS count;

-- name: RegisterUnpost :exec
-- Скасувати рухи за recorder
SELECT accum.register_unpost(@register::text, @recorder::text);

-- name: RegisterRepost :exec
-- Перепровести рухи
SELECT accum.register_repost(@register::text, @recorder::text, @data::jsonb);

-- name: RegisterBalance :one
-- Поточний баланс (jsonb — серіалізований результат)
SELECT row_to_json(b.*)::jsonb AS result
FROM accum.register_balance_internal(@register::text, @dims::jsonb, NULL) b;

-- name: RegisterBalanceAt :one  
-- Баланс на дату
SELECT row_to_json(b.*)::jsonb AS result
FROM accum.register_balance_internal(@register::text, @dims::jsonb, @at_date::timestamptz) b;

-- name: RegisterTurnover :one
-- Обороти за період
SELECT row_to_json(t.*)::jsonb AS result
FROM accum.register_turnover_internal(
    @register::text, @dims::jsonb,
    @date_from::timestamptz, @date_to::timestamptz
) t;

-- name: RegisterMovements :many
-- Журнал рухів
SELECT row_to_json(m.*)::jsonb AS movement
FROM accum.register_movements_internal(
    @register::text, @dims::jsonb,
    @date_from::timestamptz, @date_to::timestamptz,
    @lim::int, @off::int
) m;

-- name: RegisterCreate :exec
SELECT accum.register_create(
    @name::text,
    @dimensions::jsonb,
    @resources::jsonb,
    @kind::text,
    @totals_period::text,
    @partition_by::text,
    @high_write::boolean,
    @recorder_type::text
);

-- name: RegisterAlter :exec
SELECT accum.register_alter(
    @name::text,
    @add_dimensions::jsonb,
    @add_resources::jsonb,
    @high_write::boolean
);

-- name: RegisterDrop :exec
SELECT accum.register_drop(@name::text);

-- name: RegisterList :many
SELECT * FROM accum.register_list();

-- name: RegisterInfo :one
SELECT * FROM accum.register_info(@name::text);

Обробка помилок

Кастомні типи помилок з чітким маппінгом PostgreSQL RAISE EXCEPTION:

// errors.go
var (
    ErrRegisterNotFound  = &AccumError{Code: "register_not_found"}
    ErrRecorderNotFound  = &AccumError{Code: "recorder_not_found"}
    ErrRegisterExists    = &AccumError{Code: "register_exists"}
    ErrDimensionRequired = &AccumError{Code: "dimension_required"}
    ErrInvalidKind       = &AccumError{Code: "invalid_kind"}
)

type AccumError struct {
    Code    string
    Message string
    Detail  string
    Cause   error
}

func (e *AccumError) Error() string { return e.Message }
func (e *AccumError) Unwrap() error { return e.Cause }

// Використання у коді:
err := inventory.Post(ctx, ...)
var accumErr *accum.AccumError
if errors.As(err, &accumErr) {
    switch accumErr.Code {
    case "register_not_found":
        // ...
    case "dimension_required":
        // ...
    }
}

Зсередини — автоматичний парсинг PostgreSQL pgerrcode:

// internal/errors/parse.go
func parsePostgresError(err error) error {
    var pgErr *pgconn.PgError
    if !errors.As(err, &pgErr) {
        return err
    }
    switch {
    case strings.Contains(pgErr.Message, "does not exist"):
        return &AccumError{Code: "register_not_found", Message: pgErr.Message, Cause: err}
    case strings.Contains(pgErr.Message, "recorder"):
        return &AccumError{Code: "recorder_not_found", Message: pgErr.Message, Cause: err}
    default:
        return &AccumError{Code: "pg_error", Message: pgErr.Message, Cause: err}
    }
}

Performance Design

1. PostBatch — один SQL, масив рухів

register_post вже підтримує jsonb[]. go-accumulator серіалізує весь batch в один JSON-масив → один round-trip:

// Один запит замість N:
SELECT accum.register_post('inventory', $1::jsonb)
-- $1 = '[{"recorder":"...","period":"...","warehouse":1,...}, ...]'

2. Prepared Statements через pgx

При використанні pgx/v5 — автоматичне підготовлення запитів:

opts := accum.Options{
    Schema:           "accum",
    PrepareStatements: true,   // кешує prepared statements
}
client := accum.New(pool, opts)

3. Connection Pool awareness

// PgBouncer transaction-mode compatible
opts := accum.Options{
    Schema:            "accum",
    PrepareStatements: false,  // вимкнути для PgBouncer transaction mode
    StatementCacheSize: 0,
}

4. Decimal без float64

pgtype.Numeric зберігає точність:

type InventoryResources struct {
    Quantity pgtype.Numeric `json:"quantity"` // або shopspring/decimal
    Amount   pgtype.Numeric `json:"amount"`
}

ORM Compatibility

GORM

// gorm_adapter.go
func GORMAdapter(db *gorm.DB) DBTX {
    sqlDB, err := db.DB()
    if err != nil {
        panic(err)
    }
    return sqlDB // *sql.DB реалізує DBTX
}

// Транзакція з GORM
db.Transaction(func(tx *gorm.DB) error {
    txDB, _ := tx.DB()
    txAccum := client.WithTx(txDB)
    inv := accum.Use[InventoryDims, InventoryResources](txAccum, Inventory)

    // Разом з GORM операціями
    if err := tx.Create(&order).Error; err != nil {
        return err
    }
    _, err := inv.Post(ctx, InventoryMovement{
        Recorder: fmt.Sprintf("order:%d", order.ID),
        ...
    })
    return err
})

sqlx

// sqlx_adapter.go — без змін, sqlx.DB реалізує database/sql інтерфейс
client := accum.New(sqlxDB, opts)

// Транзакція
tx := sqlxDB.MustBeginTx(ctx, nil)
txAccum := client.WithTx(tx)

ent

// ent_adapter.go
entClient.Use(func(next ent.Mutator) ent.Mutator {
    return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
        v, err := next.Mutate(ctx, m)
        // пост-хук для списання зі складу
        if m.Type() == "Order" && m.Op().Is(ent.OpCreate) {
            // ...
        }
        return v, err
    })
})

CLI: accumulatorctl

Окремий CLI для управління реєстрами у CI/CD і міграціях:

# Встановлення
go install github.com/pg-accumulator/go-accumulator/v2/cmd/accumulatorctl@latest

# Генерація Go-коду з визначень реєстрів
accumulatorctl generate --def registers.go --out internal/accum/

# Інтроспекція БД → Go structs
accumulatorctl introspect \
  --dsn "$DATABASE_URL" \
  --schema accum \
  --out internal/accum/generated.go

# Перевірка розбіжностей між RegisterDef і БД
accumulatorctl diff \
  --dsn "$DATABASE_URL" \
  --def registers.go

# Генерація SQL-міграції (для golang-migrate / goose)
accumulatorctl migrate create \
  --def registers.go \
  --out migrations/

# Застосувати міграції
accumulatorctl migrate up --dsn "$DATABASE_URL"

Генерований файл (internal/accum/generated.go): ```go // Code generated by accumulatorctl. DO NOT EDIT. package accum

import accum “github.com/pg-accumulator/go-accumulator/v2”

// InventoryDims — generated from register “inventory” type InventoryDims struct { Warehouse int json:"warehouse" Product int json:"product" Lot *string json:"lot,omitempty" }

// InventoryResources — generated from register “inventory” type InventoryResources struct { Quantity decimal.Decimal json:"quantity" Amount decimal.Decimal json:"amount" }

// InventoryMovement — full movement record type InventoryMovement = accum.Movement[InventoryDims, InventoryResources]

// InventoryRegister — ready-to-use register definition var InventoryRegister = accum.RegisterDef{ Name: “inventory”, Kind: accum.Balance, TotalsPeriod: accum.Day, PartitionBy: accum.Month, Dimensions: accum.Dims{“warehouse”: accum.Int, “product”: accum.Int, “lot”: accum.Text}, Resources: accum.Res{“quantity”: accum.Numeric, “amount”: accum.Numeric}, } ```


Etапи розробки

Етап 1 — Core Library (MVP)

Ціль: Мінімальний пакет — надійні read/write операції з pg_accumulator через DBTX.

Задачі: - [ ] Ініціалізація Go-модуля (github.com/pg-accumulator/go-accumulator) - [ ] DBTX інтерфейс + адаптери для *sql.DB, *sql.Tx - [ ] AccumulatorClient.New() з Options - [ ] RegisterDef з типами Kind, Dims, Res - [ ] Use[D, R](client, def) *Register[D, R] - [ ] Register.Post() — single movement - [ ] Register.PostBatch() — masiv movements (один SQL) - [ ] Register.Unpost() - [ ] Register.Repost() - [ ] Register.Balance() / BalanceAt() - [ ] Register.Turnover() з TurnoverOptions - [ ] Register.Movements() з MovementsOptions - [ ] JSON serialization без map[string]any (struct-based) - [ ] SQL injection prevention (тільки parametrized queries, ніяки fmt.Sprintf у SQL) - [ ] Unit-тести (testify) - [ ] README з Quick Start

Результат: go get github.com/pg-accumulator/go-accumulator → повністю функціональний type-safe клієнт.


Етап 2 — pgx/v5 + Error Handling

Ціль: Native pgx адаптер та структуровані помилки.

Задачі: - [ ] pgx/v5 адаптер — WithPgxPool(), WithPgxTx() - [ ] pgxpool.Pool реалізація DBTX через bridge - [ ] Кастомні типи помилок: RegisterNotFoundError, RecorderNotFoundError, ValidationError - [ ] Парсинг PostgreSQL pgerrcode / pgconn.PgError - [ ] Підтримка context.Context скрізь (timeout, cancellation) - [ ] Prepared statements через pgx statement cache - [ ] pgtype.Numeric підтримка як альтернатива shopspring/decimal - [ ] Integration-тести з testcontainers-go + реальним pg_accumulator

Результат: Production-ready клієнт з pgx та зрозумілими помилками.


Етап 3 — Transactions + ORM Adapters

Ціль: Universal WithTx() та офіційні адаптери для популярних ORM.

Задачі: - [ ] client.WithTx(tx DBTX) *AccumulatorClient - [ ] GORM adapter: accum.GORMAdapter(*gorm.DB) DBTX - [ ] sqlx adapter: accum.SqlxAdapter(*sqlx.DB) DBTX - [ ] ent adapter: accum.EntAdapter(drv dialect.Driver) DBTX - [ ] Документація та тести для кожного адаптера

Результат: Один пакет — будь-який Go ORM.


Етап 4 — sqlc Queries + DDL Client

Ціль: Готові sqlc-файли у пакеті та повний DDL API.

Задачі: - [ ] sqlc/queries.sql — підготовлені queries для вбудовування - [ ] client.CreateRegister(ctx, RegisterDef) через register_create() - [ ] client.AlterRegister(ctx, name, AlterOptions) - [ ] client.DropRegister(ctx, name) - [ ] client.ListRegisters(ctx)[]RegisterInfo - [ ] client.RegisterInfo(ctx, name)RegisterInfo - [ ] client.Diagnostics(ctx, name)DiagnosticsResult - [ ] Документація: як вставити sqlc queries у свій проект

Результат: Повна DDL автоматизація через Go код.


Етап 5 — CLI (accumulatorctl)

Ціль: DevTools для міграцій та code generation.

Задачі: - [ ] accumulatorctl generate — Go struct/type generation з RegisterDef - [ ] accumulatorctl introspect — читання реєстрів з БД → Go code - [ ] accumulatorctl diff — порівняння коду і БД, вивід розбіжностей - [ ] accumulatorctl migrate create — генерація SQL-файлів міграцій - [ ] Підтримка golang-migrate та goose форматів міграцій - [ ] Integration з go generate директивами: go //go:generate accumulatorctl generate --def ./registers.go --out ./internal/accum/

Результат: Повний devops-цикл: код → diff → міграція → генерація типів.


Етап 6 — Advanced Features

Ціль: High-write mode, maintenance, observability.

Задачі: - [ ] HighWrite mode: client.Flush(ctx, register) — ручний delta buffer merge - [ ] client.Maintenance.RebuildTotals(ctx, name) - [ ] client.Maintenance.RebuildCache(ctx, name) - [ ] OpenTelemetry tracing — інструментація всіх операцій - [ ] Prometheus metrics: post_duration_seconds, balance_cache_hits_total - [ ] slog структурований logging - [ ] PgBouncer compatibility mode - [ ] Retry + backoff для serialization failures

Результат: Enterprise-ready пакет з observability.


Етап 7 — Ecosystem

Ціль: Документація, приклади, community.

Задачі: - [ ] pkgsite-ready документація (godoc comments) - [ ] Приклади: examples/gorm/, examples/pgx/, examples/sqlc/ - [ ] Benchmark: vs raw pgx queries (overhead measurement) - [ ] GitHub Actions: CI, lint, test (unit + integration via testcontainers) - [ ] Semantic versioning + CHANGELOG - [ ] pkg.go.dev badge

Результат: Open-source пакет готовий до публікації.


Структура пакету

go-accumulator/
├── go.mod
├── go.sum
├── README.md
├── CHANGELOG.md
│
├── accum.go                   # Public API entry: New(), Use()
├── client.go                  # AccumulatorClient
├── register.go                # Register[D, R] — generic client
├── register_def.go            # RegisterDef, Kind, Dims, Res, type constants
├── options.go                 # Options, TurnoverOptions, MovementsOptions, AlterOptions
├── errors.go                  # AccumError, sentinel errors
│
├── operations/
│   ├── post.go                # Post / PostBatch SQL execution
│   ├── unpost.go              # Unpost SQL execution
│   ├── repost.go              # Repost SQL execution
│   ├── balance.go             # Balance / BalanceAt
│   ├── turnover.go            # Turnover
│   └── movements.go           # Movements with pagination
│
├── ddl/
│   ├── create.go              # CreateRegister
│   ├── alter.go               # AlterRegister
│   ├── drop.go                # DropRegister
│   └── introspect.go          # ListRegisters, RegisterInfo, Diagnostics
│
├── adapters/
│   ├── gorm.go                # GORMAdapter
│   ├── pgx.go                 # WithPgxPool, WithPgxTx
│   ├── sqlx.go                # SqlxAdapter
│   └── ent.go                 # EntAdapter
│
├── internal/
│   ├── db/
│   │   ├── db.go              # DBTX interface (sqlc generated or manual)
│   │   └── queries.go         # Low-level parametrized query functions
│   ├── json/
│   │   └── marshal.go         # Safe JSON serialization для JSONB
│   ├── errors/
│   │   └── parse.go           # PgError → AccumError mapping
│   └── types/
│       └── decimal.go         # Decimal handling helpers
│
├── sqlc/
│   ├── queries.sql            # Ready-to-use sqlc query templates
│   └── README.md              # How to use with your sqlc setup
│
├── cmd/
│   └── accumulatorctl/
│       ├── main.go
│       ├── generate.go
│       ├── introspect.go
│       ├── diff.go
│       └── migrate.go
│
├── test/
│   ├── integration/
│   │   ├── testcontainer_test.go   # testcontainers-go setup
│   │   ├── post_test.go
│   │   ├── balance_test.go
│   │   ├── turnover_test.go
│   │   └── transaction_test.go
│   └── unit/
│       ├── json_test.go
│       ├── errors_test.go
│       └── register_def_test.go
│
└── examples/
    ├── basic-pgx/
    │   ├── main.go
    │   └── README.md
    ├── gorm-inventory/
    │   ├── main.go
    │   └── README.md
    ├── sqlc-finance/
    │   ├── main.go
    │   ├── sqlc.yaml
    │   └── README.md
    └── testcontainers/
        ├── main_test.go
        └── README.md

go.mod

module github.com/pg-accumulator/go-accumulator/v2

go 1.22

require (
    github.com/jackc/pgx/v5         v5.5.4
    github.com/shopspring/decimal    v1.3.1
)

require (
    // Optional adapters — не нав'язуємо залежності
    // Кожен адаптер у окремому sub-module або build tag
)

Ліцензія

MIT — сумісна з pgx, GORM, sqlc, ent.