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
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 connQuery (fluent, no ctx per call)
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:
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.
// 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() // permanentEscape hatch — DB()
For any GORM feature not wrapped here, DB() returns the underlying *gorm.DB bound to the context and the current query scope:
err := repository.New[User](ctx).
Where("status = ?", "active").
DB(). // *gorm.DB (ctx-bound, scoped)
Clauses(clause.OnConflict{DoNothing: true}).
CreateInBatches(rows, 100).ErrorCopy-on-write (safe branching)
Each chainable method returns a new repository over an isolated GORM session, so branches never contaminate each other:
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
page, err := repo.Order("id desc").Pagination(&core.PageOptions{Limit: 20, Page: 1})
// page.Items, page.Total, page.Count, page.Page, page.LimitTransactions
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:
type Reader[M core.IModel] interface { FindOne(...); FindAll(...); Count(); Pagination(...) }
type Writer[M core.IModel] interface { Create(...); Updates(...); Delete(...) }Custom timeout (escape hatch)
bg, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
repo.WithContext(bg).FindAll()