database

package module
v0.1.10 Latest Latest
Warning

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

Go to latest
Published: May 17, 2026 License: MIT Imports: 4 Imported by: 0

README

ligo-database

Driver-agnostic database module for Ligo, inspired by @nestjs/typeorm + TypeORM — but raw SQL, no ORM.

Go Version License Tests Coverage

Install

go get github.com/linkeunid/ligo-database

Quick Start

package main

import (
    database "github.com/linkeunid/ligo-database"
    dbpgx "github.com/linkeunid/ligo-database/pgx"
    "github.com/linkeunid/ligo"
)

func AppModule() ligo.Module {
    dbModule, err := dbpgx.PostgresModule(dbpgx.Config{
        DSN:      "postgres://user:pass@localhost:5432/mydb",
        MaxConns: 25,
        MinConns: 5,
    })
    if err != nil {
        log.Fatal(err)
    }

    return ligo.NewModule("app",
        ligo.Imports(dbModule),
        ligo.Providers(
            ligo.Factory[*UserService](NewUserService),
        ),
    )
}

Three Layers

Layer 1 — Module Setup

Register a database pool as a global injectable:

dbModule, _ := dbpgx.PostgresModule(dbpgx.Config{
    DSN: "postgres://user:pass@localhost/mydb",
})
Layer 2 — Inject database.DB Directly

Write raw SQL with full control:

type UserService struct {
    db database.DB
}

func NewUserService(db database.DB) *UserService {
    return &UserService{db: db}
}

func (s *UserService) FindByID(ctx context.Context, id int) (*User, error) {
    row := s.db.QueryRow(ctx, "SELECT id, name, email FROM users WHERE id = $1", id)
    var u User
    return &u, row.Scan(&u.ID, &u.Name, &u.Email)
}
Layer 3 — Repository Pattern with ForFeature

Typed repository with automatic transaction awareness:

type UserRepository struct {
    *database.BaseRepository
}

func NewUserRepository(db database.DB) *UserRepository {
    return &UserRepository{BaseRepository: database.NewBaseRepository(db)}
}

func (r *UserRepository) FindAll(ctx context.Context) ([]*User, error) {
    rows, err := r.Query(ctx, "SELECT id, name, email FROM users")
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    var users []*User
    for rows.Next() {
        var u User
        if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
            return nil, err
        }
        users = append(users, &u)
    }
    return users, rows.Err()
}

func UserModule() ligo.Module {
    return ligo.NewModule("user",
        ligo.Providers(
            database.ForFeature[*UserRepository](NewUserRepository),
            ligo.Factory[*UserService](NewUserService),
        ),
    )
}

Transactions

Context-scoped transactions — repos automatically use the active tx:

func (s *UserService) TransferPoints(ctx context.Context, from, to int, pts int) error {
    return database.RunInTx(ctx, s.db, func(ctx context.Context) error {
        if err := s.userRepo.DeductPoints(ctx, from, pts); err != nil {
            return err // auto-rollback
        }
        return s.userRepo.AddPoints(ctx, to, pts) // auto-commit if nil
    })
}

BaseRepository.DB(ctx) returns the active transaction from context, or the pool if no tx — repos don't need to change.

Multi-Database

Named pools for multi-database setups:

app.Register(
    dbpgx.PostgresModule(dbpgx.Config{DSN: "postgres://maindb"}),
    dbpgx.PostgresModuleNamed("analytics", dbpgx.Config{DSN: "postgres://analytics"}),
)

// Inject the registry
type AnalyticsService struct {
    registry *database.DBRegistry
}

func (s *AnalyticsService) DoWork(ctx context.Context) {
    db := s.registry.Get("analytics")
}

Driver-Specific Features

Need pgx-native features? Use Unwrap():

pool := s.db.Unwrap().(*pgxpool.Pool)
rows, _ := pool.Query(ctx, "SELECT * FROM users")
users, _ := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[User])

Health Check

checker := &database.HealthChecker{DB: db}
if err := checker.Check(ctx); err != nil {
    // database unhealthy
}

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ForFeature

func ForFeature[R any](fn func(DB) R, opts ...ligo.ProviderOption) ligo.Provider

func Module

func Module(db DB) ligo.Module

func ModuleNamed

func ModuleNamed(name string, db DB) ligo.Module

func RunInTx

func RunInTx(ctx context.Context, db DB, fn func(ctx context.Context) error) error

func WithTx

func WithTx(ctx context.Context, tx Tx) context.Context

Types

type BaseRepository

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

func NewBaseRepository

func NewBaseRepository(db DB) *BaseRepository

func (*BaseRepository) DB

func (*BaseRepository) Exec

func (r *BaseRepository) Exec(ctx context.Context, query string, args ...any) (Result, error)

func (*BaseRepository) Query

func (r *BaseRepository) Query(ctx context.Context, query string, args ...any) (Rows, error)

func (*BaseRepository) QueryRow

func (r *BaseRepository) QueryRow(ctx context.Context, query string, args ...any) Row

type DB

type DB interface {
	Querier
	Ping(ctx context.Context) error
	Close() error
	Begin(ctx context.Context) (Tx, error)
	Unwrap() any
}

type DBRegistry

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

func NewDBRegistry

func NewDBRegistry() *DBRegistry

func (*DBRegistry) Default

func (r *DBRegistry) Default() DB

func (*DBRegistry) Get

func (r *DBRegistry) Get(name string) DB

func (*DBRegistry) Register

func (r *DBRegistry) Register(name string, db DB)

func (*DBRegistry) RegisterDefault

func (r *DBRegistry) RegisterDefault(db DB)

type HealthChecker

type HealthChecker struct {
	DB DB
}

func (*HealthChecker) Check

func (h *HealthChecker) Check(ctx context.Context) error

type Querier

type Querier interface {
	Exec(ctx context.Context, query string, args ...any) (Result, error)
	Query(ctx context.Context, query string, args ...any) (Rows, error)
	QueryRow(ctx context.Context, query string, args ...any) Row
}

type Result

type Result interface {
	RowsAffected() (int64, error)
}

type Row

type Row interface {
	Scan(dest ...any) error
}

type Rows

type Rows interface {
	Next() bool
	Scan(dest ...any) error
	Close()
	Err() error
}

type Tx

type Tx interface {
	Querier
	Commit(ctx context.Context) error
	Rollback(ctx context.Context) error
}

func TxFromCtx

func TxFromCtx(ctx context.Context) (Tx, bool)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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