gormkit

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 16 Imported by: 0

README

gormkit

gormkit is a small set of composable helpers for GORM v2:

  • explicit database lifecycle management with an optional process default;
  • context-propagated required and nested transactions;
  • an error-returning generic repository for pointer-to-struct models;
  • safe ordering, association, query, and forced-zero-value update scopes;
  • opt-in tenant isolation through a model field type;
  • an optional configurable Unix timestamp plugin.

The module currently targets Go 1.23 or newer. The API is released as v0.x while real-world usage is used to validate its shape.

Install

go get github.com/Heming9/gormkit@latest

Open a database

The primary API accepts a GORM dialector and configuration, so callers retain control over the driver, logger, naming strategy, and migration behavior.

database, err := gormkit.Open(
    mysql.Open(dsn),
    &gorm.Config{Logger: logger.Default.LogMode(logger.Warn)},
    gormkit.NewTimestampPlugin(),
)
if err != nil {
    return err
}
defer database.Close()

db := database.Client(ctx)

OpenMySQL is a convenience for common MySQL connection and pool settings. It uses a silent GORM logger unless a logger is supplied.

Default database

Libraries should prefer an explicit *gormkit.Database. Applications that use a single process-wide database can install a default:

database, err := gormkit.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
    return err
}
if err := gormkit.UseDefault(database); err != nil {
    return err
}

db, err := gormkit.SQLClient(ctx)

MustSQLClient is available at application boundaries where initialization is already guaranteed. The deprecated SqlClient spelling remains temporarily for source compatibility.

Transactions

err := database.Transaction(ctx, func(txctx context.Context) error {
    db := database.Client(txctx)
    return db.Create(&record).Error
})

TPRequired is the default and reuses an existing transaction in the context. TPNested asks GORM to create a nested transaction/savepoint when a transaction already exists.

Generic repository

Repositories accept pointer-to-struct models and return database errors. Record not found errors remain compatible with errors.Is(err, gorm.ErrRecordNotFound).

repo := gormkit.NewRepository[*User](database.Client(ctx))

user := &User{Name: "example"}
if err := repo.Create(user); err != nil {
    // Create is insert-only; primary/unique conflicts are returned.
}

user, err := repo.FindByID(id)
if errors.Is(err, gorm.ErrRecordNotFound) {
    // handle absence
}

users, err := repo.
    WithScope(gormkit.OrderBy(gormkit.Desc("created_at"))).
    FindBy("status = ?", "active")

RepoOf[T](ctx) creates a repository from the process-wide default database. Conditional Save calls should target columns protected by a database unique constraint when concurrent writers are possible.

Tenant isolation

Use gormkit.TenantID as a model field to opt a model into tenant clauses:

type Project struct {
    ID       uint
    TenantID gormkit.TenantID `gorm:"column:tenant_id;index"`
    Name     string
}

ctx = gormkit.WithTenantID(ctx, 42)
err := database.Client(ctx).Create(&Project{Name: "example"}).Error

Create, query, update, and delete operations require a tenant context for these models. The tenant field is overwritten on create and omitted from updates.

Unscoped() deliberately bypasses tenant filtering as well as GORM soft-delete filtering. Treat it as a privileged operation and do not expose it to untrusted request paths.

Timestamp plugin

NewTimestampPlugin maintains ctime and mtime Unix-second columns by default. The column names and clock are configurable on TimestampPlugin.

plugin := gormkit.NewTimestampPlugin()
plugin.CreatedColumn = "created_unix"
plugin.UpdatedColumn = "updated_unix"

Tests

Default tests use a pure-Go in-memory SQLite driver:

go test ./...
go test -race ./...
go vet ./...

MySQL integration tests use the integration build tag and these environment variables: MYSQL_ADDRESS, MYSQL_USERNAME, MYSQL_PASSWORD, and MYSQL_DATABASE.

go test -tags integration ./integration

Compatibility

Before v1.0.0, minor releases may adjust APIs when doing so fixes ambiguous or unsafe behavior. Release notes document every user-visible change.

License

MIT

Documentation

Overview

Package gormkit provides small, composable helpers for GORM v2.

The package includes database lifecycle management, context-aware transactions, a generic repository, reusable scopes, tenant isolation, and an optional Unix timestamp plugin. Applications can use an explicit Database instance or install one as the process default for legacy-style package functions.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotInitialized indicates that no process-wide default database exists.
	ErrNotInitialized = errors.New("gormkit: default database is not initialized")
	// ErrNilDatabase indicates that a nil GORM database was supplied.
	ErrNilDatabase = errors.New("gormkit: database must not be nil")
)
View Source
var (
	ErrInvalidModel  = errors.New("gormkit: repository model must be a concrete type")
	ErrInvalidPaging = errors.New("gormkit: paging offset and limit must not be negative")
	ErrMultipleRows  = errors.New("gormkit: more than one row matches the save condition")
)
View Source
var ErrInvalidTransactionPropagation = errors.New("gormkit: invalid transaction propagation")
View Source
var ErrTenantRequired = errors.New("gormkit: tenant context is required")

Functions

func Asc

func Asc(name string) clause.OrderByColumn

Asc returns an ascending, quoted order column.

func Connect

func Connect(config *MySQLConfig) error

Connect is retained for source compatibility. Deprecated: use ConnectMySQL.

func ConnectMySQL

func ConnectMySQL(config *MySQLConfig) error

ConnectMySQL opens MySQL and installs it as the process-wide default database.

func Connected

func Connected() bool

Connected reports whether a process-wide default database is installed.

func Desc

func Desc(name string) clause.OrderByColumn

Desc returns a descending, quoted order column.

func RawOrder

func RawOrder(expression string) (clause.OrderByColumn, error)

RawOrder returns an explicitly raw order expression.

func Transaction

func Transaction(ctx context.Context, fn func(context.Context) error, options ...TransactionPropagation) error

Transaction executes fn using the process-wide default database.

func UseDefault

func UseDefault(db *Database) error

UseDefault installs db as the process-wide default database.

func WithTenantID

func WithTenantID(ctx context.Context, id TenantID) context.Context

WithTenantID returns a child context carrying id.

Types

type Client

type Client = *gorm.DB

Client is an alias for GORM's database handle.

func GetTransaction

func GetTransaction(ctx context.Context) Client

GetTransaction returns the transaction stored in ctx, if any.

func MustSQLClient

func MustSQLClient(ctx context.Context) Client

MustSQLClient returns the default database session bound to ctx and panics when no default database has been installed.

func SQLClient

func SQLClient(ctx context.Context) (Client, error)

SQLClient returns the default database session bound to ctx.

func SqlClient

func SqlClient(ctx context.Context) Client

SqlClient is kept for source compatibility. New code should use SQLClient or MustSQLClient so failure behavior is explicit. Deprecated: use SQLClient or MustSQLClient.

type Database

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

Database owns a configured GORM database handle.

func Default

func Default() (*Database, error)

Default returns the process-wide default database.

func Open

func Open(dialector gorm.Dialector, config *gorm.Config, plugins ...gorm.Plugin) (*Database, error)

Open creates a Database from a GORM dialector and configuration. Force update support is registered on every database opened by this function.

func OpenMySQL

func OpenMySQL(config MySQLConfig) (*Database, error)

OpenMySQL opens a MySQL database without installing it as the process default.

func Wrap

func Wrap(db *gorm.DB) (*Database, error)

Wrap creates a Database around an existing GORM handle. It does not register callbacks or plugins on the supplied handle.

func (*Database) Client

func (d *Database) Client(ctx context.Context) Client

Client returns a session bound to ctx. A nil context is treated as context.Background().

func (*Database) Close

func (d *Database) Close() error

Close closes the underlying database/sql connection pool.

func (*Database) SQLDB

func (d *Database) SQLDB() (*sql.DB, error)

SQLDB exposes the underlying database/sql connection pool.

func (*Database) Transaction

func (d *Database) Transaction(ctx context.Context, fn func(context.Context) error, options ...TransactionPropagation) error

Transaction executes fn in a transaction and propagates the transaction through the callback context.

type JSONData

type JSONData []byte

JSONData provides explicit JSON encoding helpers for byte slices.

func (*JSONData) Marshal

func (data *JSONData) Marshal(value any) error

Marshal replaces the receiver with the JSON representation of data.

func (JSONData) Unmarshal

func (data JSONData) Unmarshal(target any) error

Unmarshal decodes the receiver into target. Empty data is a no-op.

type JsonData

type JsonData = JSONData

JsonData is retained for source compatibility. Deprecated: use JSONData.

type MySQLConfig

type MySQLConfig struct {
	Username                                  string
	Password                                  string
	Address                                   string
	Database                                  string
	Charset                                   string
	TablePrefix                               string
	Location                                  *time.Location
	Logger                                    logger.Interface
	Plugins                                   []gorm.Plugin
	SingularTable                             bool
	DisableForeignKeyConstraintsWhenMigrating bool
	MaxIdleConnections                        int
	MaxOpenConnections                        int
	ConnectionMaxIdleTime                     time.Duration
	ConnectionMaxLifetime                     time.Duration
}

MySQLConfig configures the optional MySQL convenience opener.

type Paging

type Paging interface {
	Offset() int
	Limit() int
	SetTotal(total int64)
}

Paging describes offset-based pagination and receives the total row count.

type Query

type Query interface {
	Apply(db Client) Client
}

Query applies a reusable query fragment.

type Repository

type Repository[T any] interface {
	FindByID(id any) (T, error)
	FindOneBy(where any, args ...any) (T, error)
	FindLastOneBy(where any, args ...any) (T, error)
	FindByIDs(ids ...any) ([]T, error)
	FindBy(where any, args ...any) ([]T, error)
	FindAll() ([]T, error)
	Create(entity T) error
	CreateAll(entities ...T) error
	Save(entity T, where ...any) error
	SaveAll(entities ...T) error
	UpdateBy(data any, where any, args ...any) error
	DeleteBy(where any, args ...any) error
	CountBy(where ...any) (int64, error)
	Exists(where any, args ...any) (bool, error)
	WithPaging(paging Paging) Repository[T]
	WithScope(scopes ...Scope) Repository[T]
	Unscoped() Repository[T]
	DB() (Client, error)
}

Repository provides common CRUD operations for pointer-to-struct model T. Query methods return database errors instead of panicking.

func NewRepository

func NewRepository[T any](db Client) Repository[T]

NewRepository creates a repository from an explicit GORM database handle.

func RepoOf

func RepoOf[T any](ctx context.Context) Repository[T]

RepoOf creates a repository using the process-wide default database.

type Scope

type Scope = func(db Client) Client

Scope is a reusable GORM scope.

func ApplyQuery

func ApplyQuery(queries ...Query) Scope

ApplyQuery combines Query implementations into one scope.

func Force

func Force(fields ...string) Scope

Force includes the named zero-valued struct fields in an Updates operation. It has no effect when Select or Omit is already present.

func LoadAllAssociations

func LoadAllAssociations() Scope

LoadAllAssociations preloads all model associations.

func LoadAssociation

func LoadAssociation(name string, conditions ...any) Scope

LoadAssociation preloads one association with optional GORM conditions.

func LoadAssociations

func LoadAssociations(names ...string) Scope

LoadAssociations preloads the named associations.

func OrderBy

func OrderBy(columns ...clause.OrderByColumn) Scope

OrderBy applies structured order clauses. Callers must set Raw explicitly on a clause.Column when an unquoted database expression is intended.

type TenantID

type TenantID int64

TenantID is a numeric tenant identifier stored as int64.

func GetTenantID

func GetTenantID(ctx context.Context) (TenantID, bool)

GetTenantID returns the tenant stored in ctx, if any.

func (TenantID) CreateClauses

func (TenantID) CreateClauses(field *schema.Field) []clause.Interface

func (TenantID) DeleteClauses

func (TenantID) DeleteClauses(field *schema.Field) []clause.Interface

func (TenantID) Int64

func (id TenantID) Int64() int64

func (TenantID) QueryClauses

func (TenantID) QueryClauses(field *schema.Field) []clause.Interface

func (TenantID) String

func (id TenantID) String() string

func (TenantID) UpdateClauses

func (TenantID) UpdateClauses(field *schema.Field) []clause.Interface

type Timestamp

type Timestamp = int64

Timestamp is a Unix timestamp in seconds.

type TimestampPlugin

type TimestampPlugin struct {
	CreatedColumn string
	UpdatedColumn string
	Now           func() time.Time
}

TimestampPlugin maintains configurable Unix-second timestamp columns. Empty fields use ctime and mtime. A nil Now function uses time.Now.

func NewTimeAutoUpdatePlugin

func NewTimeAutoUpdatePlugin() *TimestampPlugin

NewTimeAutoUpdatePlugin is retained for source compatibility. Deprecated: use NewTimestampPlugin.

func NewTimestampPlugin

func NewTimestampPlugin() *TimestampPlugin

NewTimestampPlugin returns a timestamp plugin with conventional defaults.

func (*TimestampPlugin) Initialize

func (plugin *TimestampPlugin) Initialize(db *gorm.DB) error

func (*TimestampPlugin) Name

func (plugin *TimestampPlugin) Name() string

type TransactionPropagation

type TransactionPropagation uint8

TransactionPropagation controls how a transaction interacts with an existing transaction stored in the context.

const (
	// TPRequired reuses an existing transaction or starts a new one.
	TPRequired TransactionPropagation = iota
	// TPNested creates a nested transaction when one already exists.
	TPNested
)

Jump to

Keyboard shortcuts

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