Skip to content

Repository

Generic, GORM-backed repository. The context is bound once at New (from core.IContext) so query methods never take a ctx, and the fluent chain is copy-on-write so a base query can be branched safely.

Create a repository

go
import "gitlab.finema.co/finema/idin-core/v2/repository"

repo := repository.New[User](ctx)                    // ctx = core.IContext
repo := repository.NewWithDB[User](ctx, tx)          // bind a transaction / named conn

Query (fluent, no ctx per call)

go
user, err := repo.Where("email = ?", email).FindOne()
active, err := repo.Where("status = ?", "active").Preload("Profile").FindAll()
n, err := repo.Where("status = ?", "active").Count()

A missing row is errmsgs.NotFound:

go
if errors.Is(err, errmsgs.NotFound) { ... }

Full GORM surface

The repository wraps GORM's query-building and finishers, ctx-bound and returning core.IError:

Chainable (copy-on-write): Where, Or, Not, Order, Group, Having, Limit, Offset, Select, Omit, Distinct, Preload, Joins, InnerJoins, Table, Unscoped, Attrs, Assign, Clauses, Scopes.

Read: FindOne, Take, Last, FindAll, FindInBatches, Count, Exists, Pluck, Scan, Row, Rows, Pagination.

Write: Create, CreateInBatches, Save, Update, Updates, Delete, HardDelete, FindOneOrInit, FindOneOrCreate, Association.

Raw & tx: Raw, Exec, Transaction.

go
// reusable scopes
active := func(db *gorm.DB) *gorm.DB { return db.Where("status = ?", "active") }
users, err := repository.New[User](ctx).Scopes(active).Order("id desc").FindAll()

// aggregation into a projection
type stat struct { Status string; Total int64 }
var stats []stat
repository.New[User](ctx).Select("status, count(*) as total").Group("status").Scan(&stats)

// raw
var n int64
repository.New[User](ctx).Raw(&n, "SELECT count(*) FROM users WHERE age > ?", 18)

// soft vs hard delete
repository.New[User](ctx).Where("id = ?", id).Delete()      // soft (if model has gorm.DeletedAt)
repository.New[User](ctx).Where("id = ?", id).HardDelete()  // permanent

Escape hatch — DB()

For any GORM feature not wrapped here, DB() returns the underlying *gorm.DB bound to the context and the current query scope:

go
err := repository.New[User](ctx).
    Where("status = ?", "active").
    DB().                        // *gorm.DB (ctx-bound, scoped)
    Clauses(clause.OnConflict{DoNothing: true}).
    CreateInBatches(rows, 100).Error

Copy-on-write (safe branching)

Each chainable method returns a new repository over an isolated GORM session, so branches never contaminate each other:

go
base := repository.New[User](ctx)
active, _   := base.Where("status = ?", "active").Count()   // 2
inactive, _ := base.Where("status = ?", "inactive").Count() // 1 (independent)

No NewSession() juggling like v1.

Pagination

go
page, err := repo.Order("id desc").Pagination(&core.PageOptions{Limit: 20, Page: 1})
// page.Items, page.Total, page.Count, page.Page, page.Limit

Transactions

go
err := repo.Transaction(func(tx *gorm.DB) error {
    // use tx directly, or repository.NewWithDB[T](ctx, tx)
    return nil
})

Interface segregation

Depend on the read-only or write-only subset for easy mocking:

go
type Reader[M core.IModel] interface { FindOne(...); FindAll(...); Count(); Pagination(...) }
type Writer[M core.IModel] interface { Create(...); Updates(...); Delete(...) }

Custom timeout (escape hatch)

go
bg, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
repo.WithContext(bg).FindAll()

Maintained by Passakon Puttasuwan & Dev Core Team.