entigo

package module
v0.0.0-...-4dd8bc3 Latest Latest
Warning

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

Go to latest
Published: Feb 26, 2026 License: MIT Imports: 18 Imported by: 0

README

entigo

Go Reference Go Report Card License

A generic entity framework for Go built on GORM. Define your model once with struct tags, get automatic DTO generation, CRUD operations, optimistic locking, filtering, caching, and audit logging -- no code generation, no handwritten DTOs.

Features

  • Single struct definition -- one model drives create/update/patch/response DTOs via reflection
  • ent struct tags -- declarative field scoping with attributes (required, readonly, min, max, default)
  • Generic EntityService[T] -- type-safe CRUD with optimistic locking and transaction propagation
  • Converter[T] -- automatic DTO generation at runtime, no codegen step
  • Filter system -- query parameter operators (like:, gt:, in:, between:, etc.)
  • ConditionBuilder -- fluent, composable SQL WHERE clause construction
  • SQLBuilder -- CTE support, DISTINCT ON, paginated queries
  • Snowflake IDs -- 64-bit IDs serialized as JSON strings for JavaScript safety
  • Pluggable caching -- CacheService interface with built-in InMemCache and DummyCache
  • Audit logging -- AuditService interface with functional option event construction
  • Tracing -- Tracer/Span interface with NoopTracer default; bring your own (Sentry, OpenTelemetry)
  • Injectable context extraction -- ContextExtractor interface to customize how actor info is resolved
  • Gin integration -- ginx sub-package provides BaseHandler[S,T] for automatic REST endpoints

Installation

go get github.com/githonllc/entigo

For the Gin integration sub-package:

go get github.com/githonllc/entigo/ginx

Quick Start

1. Define a model
package models

import "github.com/githonllc/entigo"

type Article struct {
    entigo.BaseEntity
    Title   string `json:"title"   ent:"scope=create,update,response,filter"`
    Content string `json:"content" ent:"scope=create,update,response"`
    Status  string `json:"status"  ent:"scope=response,filter"`
    Views   int    `json:"views"   ent:"scope=response,filter"`
}

func (a *Article) GenerateID() entigo.ID {
    return entigo.NewID()
}
2. Create a service
opts := entigo.NewServiceOptions().
    With(entigo.OptionKeyDB, db).
    With(entigo.OptionKeyReplicaDB, replicaDB).
    With(entigo.OptionKeyCache, entigo.NewInMemCache()).
    With(entigo.OptionKeyTracer, entigo.NoopTracer{})

articleService := entigo.NewEntityService[*Article](opts)
3. CRUD operations
ctx := context.Background()

// Create
article := &Article{Title: "Hello", Content: "World"}
err := articleService.Create(ctx, article)

// Read
found, err := articleService.GetByID(ctx, article.GetID())

// Update (with optimistic locking)
found.Title = "Updated"
err = articleService.Update(ctx, found)

// Query with filters and pagination
articles, err := articleService.List(ctx,
    entigo.WithFilter(map[string]any{"status": "published"}),
    entigo.WithOrder("created_at", true),
    entigo.WithPagination(1, 10),
)

// Delete (soft delete)
err = articleService.Delete(ctx, article.GetID())

Core Concepts

Entity & BaseEntity

All models embed entigo.BaseEntity, which provides:

Field Type Description
ID entigo.ID Snowflake-based primary key
CreatedAt time.Time Auto-set on creation
UpdatedAt time.Time Auto-updated on save
DeletedAt gorm.DeletedAt Soft-delete timestamp (excluded from JSON)
Revision int Optimistic locking counter

The Entity interface provides lifecycle hooks (BeforeCreate, AfterCreate, BeforeUpdate, etc.), Validate(), soft-delete helpers (SoftDelete, Restore, IsDeleted), and revision accessors.

Override GenerateID() in your model to use custom ID generation logic. The default implementation calls entigo.NewID().

ent Tag System

The ent struct tag controls which fields appear in which DTO. Fields are tagged with scopes that the Converter reads at runtime via reflection.

Syntax:

ent:"scope=<scope1>,<scope2>(<attr1>,<attr2>=<value>)"

Scopes:

Scope Purpose
create Included in create request DTO
update Included in update request DTO
patch Included in patch request DTO (pointer fields)
response Included in response DTO
filter Field is filterable via query parameters

Attributes (within parentheses):

Attribute Example Description
readonly update(readonly) Field is present but cannot be modified
required create(required) Field must be provided
max update(max=100) Maximum value/length
min update(min=0) Minimum value/length
default create(default=active) Default value when absent

Filter options:

The filter scope supports name and col options to customize the query parameter name and database column name:

SerialNo string `json:"serial_no" ent:"scope=filter(name=sn,col=serial_number)"`
// Query: ?sn=ABC123  ->  WHERE serial_number = 'ABC123'

Example:

type User struct {
    entigo.BaseEntity
    Name   string `json:"name"   ent:"scope=create(required),update,response,filter"`
    Email  string `json:"email"  ent:"scope=create(required),update(readonly),response,filter"`
    Status string `json:"status" ent:"scope=response,filter"`
    Score  int    `json:"score"  ent:"scope=create,update(min=0,max=100),response"`
}

This single definition produces four distinct DTOs:

  • Create DTO: {name, email, score}
  • Update DTO: {name, email (readonly), score}
  • Patch DTO: {name *string, email *string, score *int} (pointer fields)
  • Response DTO: {id, created_at, updated_at, name, email, status, score}
EntityService[T]

EntityService[T] is a generic interface providing full CRUD, querying, transactions, caching, and audit logging. Create one with NewEntityService:

service := entigo.NewEntityService[*MyModel](options)

Key methods:

Method Description
Create Insert with auto-ID, validation, cache, audit
GetByID Fetch by primary key with association preloading
Update Full update with optimistic locking
Patch Partial update (map or struct with pointer fields)
Delete Soft delete
List Query with QueryOption composable filters
Query General query with options
QueryFirst Return first matching record
Count Count matching records
GetOrCreate Return existing or create new
Upsert Create or update by key column
Exec Execute raw SQL
WithTransaction Execute function within a database transaction

Optimistic locking: Every Update and Patch checks the Revision field. If another transaction modified the record, ErrConcurrentModification is returned. The revision is atomically incremented on success.

Transactions: WithTransaction stores the transaction in context. Nested service calls automatically participate in the same transaction:

err := service.WithTransaction(ctx, func(txCtx context.Context, txDb *gorm.DB) error {
    if err := serviceA.Create(txCtx, entityA); err != nil {
        return err // triggers rollback
    }
    return serviceB.Update(txCtx, entityB)
})

QueryOption composition:

results, err := service.List(ctx,
    entigo.WithWhere("status = ?", "active"),
    entigo.WithFilter(map[string]any{"name": "ilike:john"}),
    entigo.WithOrder("created_at", true),
    entigo.WithPagination(1, 25),
)
Converter[T]

Converter[T] uses reflection to generate request/response DTOs from ent scope tags at runtime. Types are computed once and cached.

converter := entigo.NewConverter[*Article](&Article{})

// Generate DTO instances
createDTO := converter.GenCreateRequest()   // only "create" scoped fields
updateDTO := converter.GenUpdateRequest()   // only "update" scoped fields
patchDTO  := converter.GenPatchRequest()    // "patch" scoped fields as pointers
respDTO   := converter.GenResponse()        // only "response" scoped fields

// Convert between model and DTO
model, err := converter.ToModel(createDTO)
resp, err  := converter.ToResponse(model)
items, err := converter.ToListResponse(models)

The flow: ent tags -> Converter reads scopes via reflection -> generates struct types at startup -> creates DTO instances at request time -> copies fields via copier.

No code generation. No separate DTO files. One struct definition drives everything.

Filter System

Filters support operator prefixes on string values. Pass them via WithFilter or SQLBuilder.ApplyFilter:

Prefix SQL Example value
(none) = ? "active"
like: LIKE ? "like:john"
ilike: LOWER() LIKE LOWER() "ilike:john"
gt: > ? "gt:100"
ge: >= ? "ge:100"
lt: < ? "lt:50"
le: <= ? "le:50"
ne: <> ? "ne:0"
in: IN (?) "in:a,b,c"
from: >= ? (RFC3339 time) "from:2024-01-01T00:00:00Z"
to: <= ? (RFC3339 time) "to:2024-12-31T23:59:59Z"
between: BETWEEN ? AND ? "between:2024-01-01T00:00:00Z,2024-12-31T23:59:59Z"
json: JSONB query "json:key=value" or "json:key~partial"
null: IS NULL "null:"
not_null: IS NOT NULL "not_null:"

Type-aware filtering from ent tags:

// Automatically parse filter scopes from struct tags and build conditions
filters := entigo.BuildFiltersForType[*Article](queryMap)
results, err := service.List(ctx, entigo.WithFilter(filters))
ConditionBuilder

Fluent API for constructing complex WHERE clauses with AND/OR groups and conditional inclusion:

cb := entigo.NewConditionBuilder().
    And("status = ?", "active").
    AndIf(entigo.NotEmpty(name), "name ILIKE ?", "%"+name+"%").
    OrGroupStart().
        And("role = ?", "admin").
        And("level > ?", 5).
    GroupEnd()

query, args := cb.Build()
// Use with service:
results, err := service.List(ctx, entigo.WithConditionBuilder(cb))

Helper check functions: NotEmpty(string), NotZero(int), NotNil(any).

SQLBuilder

For complex queries with CTEs, DISTINCT ON, and manual pagination:

qb := entigo.NewSQLBuilder().
    With("recent", "SELECT * FROM articles WHERE created_at > ?", cutoff).
    Select("*").
    From("recent").
    Where("status = ?", "published").
    OrderBy("created_at DESC")

total, results, err := entigo.ExecutePaginatedQuery[Article](ctx, db, qb, offset, size)

Supports DistinctOn (PostgreSQL), GroupBy, ApplyFilter, and nested CTEs via WithBuilder.

ContextExtractor

ContextExtractor is an interface that allows you to customize how entigo resolves actor identity from context.Context. This is useful when your application uses a different context layout than the built-in CtxKey* constants.

type ContextExtractor interface {
    Extract(ctx context.Context) ActorInfo
}

ActorInfo contains:

Field Type Description
ActorType ActorType User, API key, etc.
ActorID ID The authenticated actor's ID
IdentityID ID Underlying account/identity ID
IsAdmin bool Whether the actor is an admin
IPAddress string Client IP address
UserAgent string User-Agent header value

Injecting a custom extractor:

opts := entigo.NewServiceOptions().
    With(entigo.OptionKeyDB, db).
    With(entigo.OptionKeyReplicaDB, replicaDB).
    With(entigo.OptionKeyContextExtractor, myCustomExtractor)

If no extractor is provided, a default implementation reads from the built-in CtxKeyUserID, CtxKeyApiKeyID, CtxKeyIsAdmin, etc. context keys.

ID Generation

IDs are snowflake-based 64-bit integers (entigo.ID). They serialize to JSON as strings to avoid JavaScript precision loss.

// Initialize with a node ID (0-1023). Call once at startup.
entigo.InitIDGenerator(1)

// Generate IDs
id := entigo.NewID()

// Parse from various types
id, err := entigo.ParseID("1234567890")
id, err := entigo.ParseID(int64(1234567890))

// Validate
if entigo.IsInvalidID(id) { ... }

If InitIDGenerator is not called, the generator auto-initializes with node 0 on first use.

Caching

The CacheService interface provides string-based key-value caching with a 5-minute default TTL.

type CacheService interface {
    Get(key string) (string, error)
    Set(key string, value string) error
    Delete(key string) error
}

Built-in implementations:

Type Description
InMemCache Thread-safe in-memory cache with lazy TTL expiration
DummyCache No-op cache (Get always returns ErrCacheMiss)

Object caching helpers:

// Store and retrieve typed objects (JSON serialized)
entigo.SetObjectCache(cache, "user:123", user)
entigo.GetObjectCache(cache, "user:123", &user)

// With custom TTL (requires CacheServiceWithExp)
entigo.SetObjectCacheExp(cache, "key", value, 10*time.Minute)
Audit Logging

The AuditService interface records entity operation events. Events are constructed with functional options:

type AuditService interface {
    LogEvent(ctx context.Context, entry *AuditLogEvent)
}
event := entigo.NewAuditLogEvent(
    entigo.WithAction("CREATE"),
    entigo.WithResourceType("article"),
    entigo.WithResourceID(id),
    entigo.WithActorType(entigo.ActorTypeUser),
    entigo.WithActorID(userID),
    entigo.WithDetails(map[string]any{"title": "New Article"}),
)
auditService.LogEvent(ctx, event)

EntityService automatically logs audit events for Create, Update, Patch, and Delete operations. Use NewDummyAuditService() to disable.

Tracing

The Tracer interface creates spans for tracking operations:

type Tracer interface {
    StartSpan(ctx context.Context, operation string) Span
}

type Span interface {
    Finish()
}

Use NoopTracer{} as the default. Implement the interface to integrate with Sentry, OpenTelemetry, Datadog, or any tracing backend. All EntityService and BaseHandler methods create spans automatically.

Context Keys

ContextKey is a typed string for storing values in context.Context. Built-in keys:

Key Type Description
CtxKeyUserID ID Authenticated user ID
CtxKeyIdentityID ID Underlying account/identity ID
CtxKeyApiKeyID ID API key used for authentication
CtxKeyIsAdmin bool Whether the user is an admin
CtxKeyRealIP string Client IP from proxy headers
CtxKeyUserAgent string User-Agent header value
CtxKeyClientIP string Direct connection IP

Define custom keys for your domain:

const CtxKeyTenantID entigo.ContextKey = "myapp.tenant_id"

Gin Integration (ginx)

The github.com/githonllc/entigo/ginx sub-package provides Gin-specific HTTP handler support.

BaseHandler[S,T]

BaseHandler provides ready-to-use HTTP handler methods for standard REST operations:

handler := ginx.NewBaseHandler[ArticleService, *Article](articleService)

r := gin.Default()
r.GET("/articles",     handler.List)     // paginated list with filters
r.GET("/articles/:id", handler.Get)      // get by ID
r.POST("/articles",    handler.Create)   // create from request body
r.PUT("/articles/:id", handler.Update)   // full update
r.PATCH("/articles/:id", handler.Patch)  // partial update
r.DELETE("/articles/:id", handler.Delete)// soft delete
r.GET("/articles/export", handler.Export)// CSV export

List automatically parses ?page=, ?size=, ?order=, and filter query parameters. Request bodies are automatically bound to the appropriate DTO (create, update, or patch) generated by the Converter.

HandlerHooks

Customize lifecycle behavior without subclassing:

handler := ginx.NewBaseHandler[ArticleService, *Article](articleService)
handler.Hooks = ginx.HandlerHooks[*Article]{
    BeforeCreate: func(c *gin.Context, entity *Article) error {
        entity.Status = "draft"
        return nil
    },
    AfterCreate: func(c *gin.Context, entity *Article) error {
        // send notification, etc.
        return nil
    },
    BeforeUpdate: func(c *gin.Context, entity *Article) error { ... },
    AfterUpdate:  func(c *gin.Context, entity *Article) error { ... },
    BeforePatch:  func(c *gin.Context, input any) error { ... },
    AfterPatch:   func(c *gin.Context, input any) error { ... },
}
Error Handling

CustomError carries an HTTP status code, error code, and message:

type CustomError struct {
    Message    string
    ErrorCode  int
    HTTPStatus int
}

Predefined errors: ErrBadRequest, ErrUnauthorized, ErrForbidden, ErrNotFound, ErrTooManyRequests, ErrInternalServer.

Error constructors: NewBadRequestError(msg), NewNotFoundError(msg), NewConflictError(msg), etc.

HandleDBError translates GORM and PostgreSQL errors into CustomError values:

  • gorm.ErrRecordNotFound -> 404
  • gorm.ErrDuplicatedKey / PG code 23505 -> 400 (duplicate)
  • PG code 23503 -> 400 (foreign key violation)
  • context.Canceled / context.DeadlineExceeded -> 408

ResponseError sends the appropriate JSON error response based on the error type.

Response Format

All responses use a standard JSON envelope:

{
    "ok": true,
    "message": "optional message",
    "data": { ... },
    "count": 100
}

Error responses:

{
    "ok": false,
    "message": "error description",
    "error_code": 40400
}
CSV Export

The Export handler and ToCSV function convert response DTOs to CSV:

data, err := ginx.ToCSV(entities, ginx.WithComma(ginx.SemicolonSeparator))

Options: WithComma(rune) for custom delimiters, WithUseCRLF(bool) for line endings.

Helpers
  • RequireContext(c) -- creates a context.Context populated with auth metadata from Gin context
  • QueryParamsToQueryMap(c) -- converts Gin query params to entigo.QueryMap
  • GetIDFromContext(c, key) -- retrieves an entigo.ID from Gin context
  • IsAdmin(c), IsCurrentUser(c, id), IsAdminOrCurrentUser(c, id) -- auth checks

Configuration

Wire up dependencies via ServiceOptions:

opts := entigo.NewServiceOptions()

// Required: primary and replica database connections
opts.With(entigo.OptionKeyDB, primaryDB)
opts.With(entigo.OptionKeyReplicaDB, replicaDB)

// Optional: caching (defaults to DummyCache)
opts.With(entigo.OptionKeyCache, entigo.NewInMemCache())

// Optional: audit logging (defaults to DummyAuditService)
opts.With(entigo.OptionKeyAudit, myAuditService)

// Optional: distributed tracing (defaults to NoopTracer)
opts.With(entigo.OptionKeyTracer, myTracer)

// Optional: custom context extraction (defaults to built-in CtxKey* reader)
opts.With(entigo.OptionKeyContextExtractor, myExtractor)

// Debug flags
opts.DebugMode = true    // log all operations
opts.DebugSQLMode = true // log SQL queries

// Clone for a second service with different settings
opts2 := opts.Clone()
opts2.With(entigo.OptionKeyDB, anotherDB)

Available option keys:

Key Type Required Description
OptionKeyDB *gorm.DB Yes Primary database
OptionKeyReplicaDB *gorm.DB Yes Read replica database
OptionKeyCache CacheService No Caching backend
OptionKeyAudit AuditService No Audit log backend
OptionKeyTracer Tracer No Distributed tracing
OptionKeyContextExtractor ContextExtractor No Actor info extraction
OptionKeyServiceContext any No Custom service context

Dependencies

Package Purpose
gorm.io/gorm ORM for database operations
github.com/bwmarrin/snowflake Snowflake ID generation
github.com/jinzhu/copier Struct-to-struct field copying
github.com/gin-gonic/gin HTTP framework (ginx sub-package)
github.com/jackc/pgx/v5 PostgreSQL driver (error handling)
github.com/gocarina/gocsv CSV marshaling (ginx sub-package)

Contributing

Contributions are welcome. Please open an issue to discuss proposed changes before submitting a pull request. All code, comments, and commit messages must be in English.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Commit your changes
  4. Push to the branch and open a pull request

License

MIT -- see LICENSE for details.

Documentation

Overview

Package entigo provides a generic entity framework for Go built on GORM.

It features dynamic DTO generation via reflection-based struct tags, generic CRUD operations with optimistic locking, flexible query filtering, caching, audit logging, tracing, and snowflake-based ID generation.

Key types

Module path

github.com/githonllc/entigo

The ginx sub-package (github.com/githonllc/entigo/ginx) provides Gin-specific HTTP handler support via [BaseHandler].

Index

Constants

View Source
const (
	// DefaultPageSize is the default number of items returned per page in list queries.
	DefaultPageSize = 25

	// MaxPageSize is the maximum number of items allowed per page in list queries.
	MaxPageSize = 1000

	// MaxExportSize is the maximum number of items allowed in a single export operation.
	MaxExportSize = 1000
)

Variables

View Source
var (
	// ErrCacheMiss indicates the requested key was not found in the cache.
	ErrCacheMiss = errors.New("cache miss")

	// ErrEntityNil indicates that a nil entity was passed where a non-nil value is required.
	ErrEntityNil = errors.New("entity is nil")

	// ErrEntityAlreadyDeleted indicates an attempt to delete an entity that has already been soft-deleted.
	ErrEntityAlreadyDeleted = errors.New("entity already deleted")

	// ErrNoRowsDeleted indicates that a delete operation affected zero rows.
	ErrNoRowsDeleted = errors.New("no rows deleted")

	// ErrConcurrentModification indicates an optimistic locking conflict
	// where the entity was modified by another transaction.
	ErrConcurrentModification = errors.New("concurrent modification detected")

	// ErrPermissionDenied indicates that the caller lacks permission for the requested operation.
	ErrPermissionDenied = errors.New("permission denied")

	// ErrFieldNotFound indicates that the specified field does not exist on the entity.
	ErrFieldNotFound = errors.New("field not found")
)

Functions

func ArgsToMap

func ArgsToMap(args ...any) map[string]any

ArgsToMap converts variadic key-value pairs to a map. Keys must be strings; non-string keys are silently skipped. If the number of arguments is odd, an empty map is returned and a warning is logged.

Usage:

ArgsToMap("name", "john", "age", 18) -> map[string]any{"name": "john", "age": 18}

func BuildFilters

func BuildFilters(queryMap QueryMap, scopes []FilterScope) map[string]any

BuildFilters converts a QueryMap to a filter map based on FilterScopes. The returned map uses column names as keys and can be used with WithFilter or SQLBuilder.ApplyFilter.

func BuildFiltersForType

func BuildFiltersForType[T any](params QueryMap) map[string]any

BuildFiltersForType builds a filter map for a given struct type using its ent tag filter scopes and the provided query parameters.

func Copy

func Copy(from, to any) error

Copy copies fields from src to dst using the copier library. Returns an error instead of panicking on failure.

func CreateTypeFromScopeTag

func CreateTypeFromScopeTag(model any, scope string, usePointer bool) reflect.Type

CreateTypeFromScopeTag creates a new reflect.Type based on the model and scope. It filters fields by scope tag, handles embedded structs, and caches the result for thread-safe reuse.

func ExecutePaginatedQuery

func ExecutePaginatedQuery[T any](
	ctx context.Context,
	db *gorm.DB,
	qb *SQLBuilder,
	offset, size int,
) (int64, []T, error)

ExecutePaginatedQuery is a generic paginated query executor. It runs a count query to determine the total number of matching records, then fetches the requested page using LIMIT/OFFSET.

func GetDbFieldName

func GetDbFieldName(field reflect.StructField) string

GetDbFieldName extracts the database column name from a struct field. It checks the gorm tag first (for column: directive), then falls back to the json tag, and finally converts the Go field name to snake_case.

func GetDbFields

func GetDbFields(s any) []string

GetDbFields extracts all database field names from a struct, including fields from embedded structs.

func GetJSONName

func GetJSONName(field reflect.StructField) string

GetJSONName returns the JSON field name for a struct field. It reads the "json" struct tag; if absent or set to "-", it falls back to converting the Go field name to snake_case.

func GetObjectCache

func GetObjectCache[T any](cache CacheService, key string, dest *T) error

GetObjectCache retrieves a JSON-serialized object from the cache and unmarshals it into dest.

func GetOrDefault

func GetOrDefault[T comparable](value, defaultValue T) T

GetOrDefault returns value if it is not the zero value for its type; otherwise it returns defaultValue.

func GetScopeAttributeValue

func GetScopeAttributeValue(tag, scope, attrName string) (string, bool)

GetScopeAttributeValue returns the value of a specific attribute in the given scope. The second return value indicates whether the attribute was found.

func GetScopeFields

func GetScopeFields(s any, action string) []string

GetScopeFields extracts the database field names for fields that have the specified scope.

func HasScopeAttribute

func HasScopeAttribute(tag, scope, attrName string) bool

HasScopeAttribute checks whether a specific attribute exists in the given scope.

func InitIDGenerator

func InitIDGenerator(nodeID int64) error

InitIDGenerator initializes the snowflake node with the given nodeID (0-1023). It is safe to call multiple times; only the first call takes effect. If not called before NewID, the generator auto-initializes with node 0.

func IsEmptyStructType

func IsEmptyStructType(t reflect.Type) bool

IsEmptyStructType checks if the given reflect.Type represents an empty struct (zero fields).

func IsInvalidID

func IsInvalidID(id any) bool

IsInvalidID reports whether the given value represents an invalid (non-positive) ID. Supported input types: ID, int64, string.

func IsNil

func IsNil(i any) bool

IsNil checks whether the given value is nil or a nil-valued nillable type (pointer, map, slice, channel, function, or interface).

func IsValidColumnName

func IsValidColumnName(name string) bool

IsValidColumnName checks whether a string is a safe SQL column/field name. Prevents SQL injection via field name interpolation.

func NewInstance

func NewInstance[T any]() T

NewInstance creates a new instance of the generic type T. If T is a pointer type, it allocates and returns a pointer to a new zero value. If T is a value type, it returns a new zero value.

func NotEmpty

func NotEmpty(value string) func() bool

NotEmpty returns a check function that returns true when the string value is non-empty.

func NotNil

func NotNil(value any) func() bool

NotNil returns a check function that returns true when the value is not nil.

func NotZero

func NotZero(value int) func() bool

NotZero returns a check function that returns true when the int value is non-zero.

func ParseIntOrDefault

func ParseIntOrDefault(s string, defaultVal int) int

ParseIntOrDefault converts a string to int, returning defaultVal if parsing fails.

func SetObjectCache

func SetObjectCache(cache CacheService, key string, value any) error

SetObjectCache stores an object in the cache by marshaling it to JSON, using the default expiration.

func SetObjectCacheExp

func SetObjectCacheExp(cache CacheService, key string, value any, exp time.Duration) error

SetObjectCacheExp stores an object in the cache with a specific TTL. It requires a CacheServiceWithExp implementation; otherwise it falls back to Set.

func ToSnakeCase

func ToSnakeCase(s string) string

ToSnakeCase converts PascalCase to snake_case, handling acronyms correctly. e.g. "APIKey" -> "api_key", "ProofOfDelivery" -> "proof_of_delivery".

Types

type ActorInfo

type ActorInfo struct {
	ActorType  ActorType
	ActorID    ID
	IdentityID ID
	IsAdmin    bool
	IPAddress  string
	UserAgent  string
}

ActorInfo holds the identity and metadata of the actor performing an operation. It is extracted from context.Context by a ContextExtractor and used for access control and audit logging.

type ActorType

type ActorType string

ActorType identifies the type of actor performing an audited operation.

const (
	ActorTypeSystem ActorType = "SYSTEM"
	ActorTypeUser   ActorType = "USER"
	ActorTypeApiKey ActorType = "API_KEY"
)

type AuditLogEvent

type AuditLogEvent struct {
	ActorType    ActorType
	ActorID      ID
	IdentityID   ID
	ResourceType string
	ResourceID   ID
	Action       string
	Status       string
	Details      map[string]any
	IPAddress    string
	UserAgent    string
	ErrorMessage string
}

AuditLogEvent represents a single auditable action with contextual metadata.

func NewAuditLogEvent

func NewAuditLogEvent(opts ...AuditLogEventOption) *AuditLogEvent

NewAuditLogEvent creates a new AuditLogEvent with the given functional options applied.

func (AuditLogEvent) ToMap

func (e AuditLogEvent) ToMap() map[string]any

ToMap converts the event to a flat map, suitable for structured logging or storage.

type AuditLogEventOption

type AuditLogEventOption func(*AuditLogEvent)

AuditLogEventOption is a functional option for configuring an AuditLogEvent.

func WithAction

func WithAction(action string) AuditLogEventOption

WithAction sets the action name (e.g., "create", "delete") on the audit event.

func WithActorID

func WithActorID(actorID ID) AuditLogEventOption

WithActorID sets the actor ID on the audit event.

func WithActorType

func WithActorType(actorType ActorType) AuditLogEventOption

WithActorType sets the actor type on the audit event.

func WithDetails

func WithDetails(details map[string]any) AuditLogEventOption

WithDetails sets the additional details map on the audit event.

func WithErrorMessage

func WithErrorMessage(errorMessage string) AuditLogEventOption

WithErrorMessage sets the error message on the audit event, typically used when the audited operation failed.

func WithIPAddress

func WithIPAddress(ipAddress string) AuditLogEventOption

WithIPAddress sets the client IP address on the audit event.

func WithIdentityID

func WithIdentityID(identityID ID) AuditLogEventOption

WithIdentityID sets the identity ID on the audit event.

func WithResourceID

func WithResourceID(resourceID ID) AuditLogEventOption

WithResourceID sets the resource ID on the audit event.

func WithResourceType

func WithResourceType(resourceType string) AuditLogEventOption

WithResourceType sets the resource type (e.g., "user", "device") on the audit event.

func WithStatus

func WithStatus(status string) AuditLogEventOption

WithStatus sets the status (e.g., "success", "failure") on the audit event.

func WithUserAgent

func WithUserAgent(userAgent string) AuditLogEventOption

WithUserAgent sets the client user agent string on the audit event.

type AuditService

type AuditService interface {
	LogEvent(ctx context.Context, entry *AuditLogEvent)
}

AuditService records audit log events for entity operations.

func NewDummyAuditService

func NewDummyAuditService() AuditService

NewDummyAuditService creates an AuditService that silently discards all audit events. Useful for testing or when audit logging is not needed.

type BaseEntity

type BaseEntity struct {
	ID        ID             `json:"id" gorm:"primaryKey;<-:create" ent:"scope=response,filter"`
	CreatedAt time.Time      `json:"created_at" gorm:"<-:create" ent:"scope=response,filter"`
	UpdatedAt time.Time      `json:"updated_at" ent:"scope=response,filter"`
	DeletedAt gorm.DeletedAt `json:"-" gorm:"index;<-:update"`
	Revision  int            `json:"-" gorm:"default:0"`
}

BaseEntity provides a default implementation of the Entity interface. Embed this struct in domain models to get standard fields and behavior.

func (*BaseEntity) AfterCreate

func (e *BaseEntity) AfterCreate(tx *gorm.DB) error

AfterCreate is a GORM hook called after inserting a new record.

func (*BaseEntity) AfterDelete

func (e *BaseEntity) AfterDelete(tx *gorm.DB) error

AfterDelete is a GORM hook called after deleting a record.

func (*BaseEntity) AfterFind

func (e *BaseEntity) AfterFind(tx *gorm.DB) error

AfterFind is a GORM hook called after querying a record.

func (*BaseEntity) AfterUpdate

func (e *BaseEntity) AfterUpdate(tx *gorm.DB) error

AfterUpdate is a GORM hook called after updating a record.

func (*BaseEntity) BeforeCreate

func (e *BaseEntity) BeforeCreate(tx *gorm.DB) error

BeforeCreate is a GORM hook called before inserting a new record. It initializes the entity if no valid ID is set and runs validation.

func (*BaseEntity) BeforeDelete

func (e *BaseEntity) BeforeDelete(tx *gorm.DB) error

BeforeDelete is a GORM hook called before deleting a record.

func (*BaseEntity) BeforeUpdate

func (e *BaseEntity) BeforeUpdate(tx *gorm.DB) error

BeforeUpdate is a GORM hook called before updating a record. It updates the timestamp and runs validation.

func (*BaseEntity) GenerateID

func (e *BaseEntity) GenerateID() ID

GenerateID generates a new unique ID using the default ID generator.

func (*BaseEntity) GetCreatedAt

func (e *BaseEntity) GetCreatedAt() time.Time

GetCreatedAt returns the creation timestamp.

func (*BaseEntity) GetDeletedAt

func (e *BaseEntity) GetDeletedAt() *time.Time

GetDeletedAt returns the soft-delete timestamp, or nil if not deleted.

func (*BaseEntity) GetEntityName

func (e *BaseEntity) GetEntityName() string

GetEntityName returns the fully-qualified type name of the entity.

func (*BaseEntity) GetID

func (e *BaseEntity) GetID() ID

GetID returns the entity ID.

func (*BaseEntity) GetRevision

func (e *BaseEntity) GetRevision() int

GetRevision returns the current revision number for optimistic locking.

func (*BaseEntity) GetUpdatedAt

func (e *BaseEntity) GetUpdatedAt() time.Time

GetUpdatedAt returns the last update timestamp.

func (*BaseEntity) IncrementRevision

func (e *BaseEntity) IncrementRevision()

IncrementRevision increments the revision number.

func (*BaseEntity) Init

func (e *BaseEntity) Init()

Init initializes the entity with a new ID and timestamps. Called automatically by BeforeCreate if no valid ID is set.

func (*BaseEntity) IsDeleted

func (e *BaseEntity) IsDeleted() bool

IsDeleted returns true if the entity has been soft-deleted.

func (*BaseEntity) NewInstance

func (e *BaseEntity) NewInstance() Entity

NewInstance creates a new BaseEntity instance.

func (*BaseEntity) Restore

func (e *BaseEntity) Restore()

Restore clears the soft-delete marker, restoring the entity.

func (*BaseEntity) SetCreatedAt

func (e *BaseEntity) SetCreatedAt(t time.Time)

SetCreatedAt sets the creation timestamp.

func (*BaseEntity) SetID

func (e *BaseEntity) SetID(id ID)

SetID sets the entity ID.

func (*BaseEntity) SetUpdatedAt

func (e *BaseEntity) SetUpdatedAt(t time.Time)

SetUpdatedAt sets the last update timestamp.

func (*BaseEntity) SoftDelete

func (e *BaseEntity) SoftDelete()

SoftDelete marks the entity as soft-deleted with the current UTC time.

func (*BaseEntity) Validate

func (e *BaseEntity) Validate() error

Validate performs entity validation. Override in embedding structs for custom logic.

type CacheService

type CacheService interface {
	// Get retrieves the value for the given key.
	// Returns ErrCacheMiss if the key does not exist or has expired.
	Get(key string) (string, error)

	// Set stores a value with the default expiration (5 minutes).
	Set(key string, value string) error

	// Delete removes the value for the given key.
	Delete(key string) error
}

CacheService defines the interface for a string-based key-value cache.

func NewDummyCache

func NewDummyCache() CacheService

NewDummyCache creates a new DummyCache.

func NewInMemCache

func NewInMemCache() CacheService

NewInMemCache creates a new in-memory cache.

type CacheServiceWithExp

type CacheServiceWithExp interface {
	CacheService
	SetWithExp(key string, value string, exp time.Duration) error
}

CacheServiceWithExp extends CacheService with explicit expiration support.

type Condition

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

Condition represents a single SQL condition or a group of nested conditions.

type ConditionBuilder

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

ConditionBuilder provides a fluent API for constructing complex SQL WHERE clauses with support for AND/OR operators, nested groups, and conditional inclusion.

func BuildFilterConditions

func BuildFilterConditions(params QueryMap, scopes []FilterScope) *ConditionBuilder

BuildFilterConditions builds query conditions from query parameters based on filter scopes. It returns a ConditionBuilder that can be used with WithConditionBuilder to apply the conditions to a GORM query.

For multi-value parameters, an IN clause is generated. For single string values, wildcards (* and ?) are converted to SQL LIKE patterns. Boolean, integer, unsigned integer, and float types are parsed and matched exactly.

func NewConditionBuilder

func NewConditionBuilder() *ConditionBuilder

NewConditionBuilder creates a new ConditionBuilder with an AND root group.

func ParseAndBuildFilters

func ParseAndBuildFilters[T any](params QueryMap) *ConditionBuilder

ParseAndBuildFilters combines parsing filter scopes from a type and building conditions from query parameters in a single call.

func (*ConditionBuilder) And

func (cb *ConditionBuilder) And(args ...any) *ConditionBuilder

And adds a condition joined with the AND operator.

func (*ConditionBuilder) AndIf

func (cb *ConditionBuilder) AndIf(check func() bool, args ...any) *ConditionBuilder

AndIf adds an AND condition only when the check function returns true.

func (*ConditionBuilder) Build

func (cb *ConditionBuilder) Build() (string, []any)

Build generates the final SQL WHERE clause and its arguments. Returns an empty string and nil args if no conditions have been added.

func (*ConditionBuilder) Condition

func (cb *ConditionBuilder) Condition(args ...any) *ConditionBuilder

Condition adds one or more conditions without specifying an operator. When multiple pairs of (query, args) are provided, they are each added as AND conditions. A trailing single argument is also added as an AND condition.

func (*ConditionBuilder) ConditionIf

func (cb *ConditionBuilder) ConditionIf(check func() bool, args ...any) *ConditionBuilder

ConditionIf adds a condition only when the check function returns true.

func (*ConditionBuilder) GroupEnd

func (cb *ConditionBuilder) GroupEnd() *ConditionBuilder

GroupEnd closes the current nested group and returns to the parent scope.

func (*ConditionBuilder) GroupStart

func (cb *ConditionBuilder) GroupStart() *ConditionBuilder

GroupStart begins a new nested AND group. All subsequent conditions are added to this group until GroupEnd is called.

func (*ConditionBuilder) HasConditions

func (cb *ConditionBuilder) HasConditions() bool

HasConditions returns true if the builder contains at least one condition.

func (*ConditionBuilder) IsEmpty

func (cb *ConditionBuilder) IsEmpty() bool

IsEmpty returns true if the builder has no conditions.

func (*ConditionBuilder) Or

func (cb *ConditionBuilder) Or(args ...any) *ConditionBuilder

Or adds a condition joined with the OR operator.

func (*ConditionBuilder) OrGroupStart

func (cb *ConditionBuilder) OrGroupStart() *ConditionBuilder

OrGroupStart begins a new nested OR group. All subsequent conditions are added to this group until GroupEnd is called.

func (*ConditionBuilder) OrIf

func (cb *ConditionBuilder) OrIf(check func() bool, args ...any) *ConditionBuilder

OrIf adds an OR condition only when the check function returns true.

type ContextExtractor

type ContextExtractor interface {
	Extract(ctx context.Context) ActorInfo
}

ContextExtractor extracts actor information from a request context. Implement this interface to customize how entigo resolves the current user, admin status, IP address, and other metadata from your application's context.

Inject via ServiceOptions:

opts.With(entigo.OptionKeyContextExtractor, myExtractor)

type ContextKey

type ContextKey string

ContextKey is a typed key for storing and retrieving values from context.Context. Using a distinct type prevents collisions with keys from other packages. Consumers can define their own domain-specific keys using this type.

const (
	CtxKeyUserID     ContextKey = "entigo.user_id"
	CtxKeyIdentityID ContextKey = "entigo.identity_id"
	CtxKeyApiKeyID   ContextKey = "entigo.api_key_id"
	CtxKeyIsAdmin    ContextKey = "entigo.is_admin"
	CtxKeyRealIP     ContextKey = "entigo.real_ip"
	CtxKeyUserAgent  ContextKey = "entigo.user_agent"
	CtxKeyClientIP   ContextKey = "entigo.client_ip"
)

Built-in context key constants. These are used by the defaultContextExtractor and the ginx.RequireContext helper. If you provide a custom ContextExtractor, you do not need to use these keys.

type Converter

type Converter[T any] struct {
	// contains filtered or unexported fields
}

Converter handles conversion between DTOs and models using reflection. It pre-computes the response, create, update, and patch types from the model's ent scope tags for efficient runtime DTO generation.

func NewConverter

func NewConverter[T any](model T) *Converter[T]

NewConverter creates a new Converter instance with pre-computed types derived from the model's ent scope tags.

func (*Converter[T]) GenCreateRequest

func (c *Converter[T]) GenCreateRequest() any

GenCreateRequest generates a new create request DTO instance.

func (*Converter[T]) GenPatchRequest

func (c *Converter[T]) GenPatchRequest() any

GenPatchRequest generates a new patch request DTO instance. Patch DTOs use pointer fields to distinguish between zero values and absent fields.

func (*Converter[T]) GenResponse

func (c *Converter[T]) GenResponse() any

GenResponse generates a new response DTO instance.

func (*Converter[T]) GenUpdateRequest

func (c *Converter[T]) GenUpdateRequest() any

GenUpdateRequest generates a new update request DTO instance.

func (*Converter[T]) GetCreateType

func (c *Converter[T]) GetCreateType() reflect.Type

GetCreateType returns the pre-computed create struct type.

func (*Converter[T]) GetPatchType

func (c *Converter[T]) GetPatchType() reflect.Type

GetPatchType returns the pre-computed patch struct type.

func (*Converter[T]) GetResponseType

func (c *Converter[T]) GetResponseType() reflect.Type

GetResponseType returns the pre-computed response struct type.

func (*Converter[T]) GetUpdateType

func (c *Converter[T]) GetUpdateType() reflect.Type

GetUpdateType returns the pre-computed update struct type.

func (*Converter[T]) HasCreateType

func (c *Converter[T]) HasCreateType() bool

HasCreateType returns true if the model has fields tagged with the "create" scope.

func (*Converter[T]) HasPatchType

func (c *Converter[T]) HasPatchType() bool

HasPatchType returns true if the model has fields tagged with the "patch" scope.

func (*Converter[T]) HasResponseType

func (c *Converter[T]) HasResponseType() bool

HasResponseType returns true if the model has fields tagged with the "response" scope.

func (*Converter[T]) HasUpdateType

func (c *Converter[T]) HasUpdateType() bool

HasUpdateType returns true if the model has fields tagged with the "update" scope.

func (*Converter[T]) NewModelInstance

func (c *Converter[T]) NewModelInstance() T

NewModelInstance creates a new zero-value instance of the model type T.

func (*Converter[T]) ToExistingModel

func (c *Converter[T]) ToExistingModel(input any, model *T) error

ToExistingModel converts input data into an existing model instance.

func (*Converter[T]) ToListResponse

func (c *Converter[T]) ToListResponse(models []T) ([]any, error)

ToListResponse converts a slice of models to a slice of response DTOs.

func (*Converter[T]) ToModel

func (c *Converter[T]) ToModel(input any) (T, error)

ToModel converts input data to a new model instance of type T.

func (*Converter[T]) ToResponse

func (c *Converter[T]) ToResponse(model T) (any, error)

ToResponse converts a model to a response DTO.

type DummyCache

type DummyCache struct{}

DummyCache is a no-operation cache that never stores anything. Get always returns ErrCacheMiss; Set and Delete are silent no-ops.

func (*DummyCache) Delete

func (c *DummyCache) Delete(key string) error

func (*DummyCache) Get

func (c *DummyCache) Get(key string) (string, error)

func (*DummyCache) Set

func (c *DummyCache) Set(key string, value string) error

type Entity

type Entity interface {
	NewInstance() Entity
	GenerateID() ID

	GetEntityName() string

	GetID() ID
	SetID(id ID)
	GetCreatedAt() time.Time
	SetCreatedAt(t time.Time)
	GetUpdatedAt() time.Time
	SetUpdatedAt(t time.Time)
	GetDeletedAt() *time.Time

	IsDeleted() bool
	SoftDelete()
	Restore()

	Validate() error

	BeforeCreate(tx *gorm.DB) error
	AfterCreate(tx *gorm.DB) error
	BeforeUpdate(tx *gorm.DB) error
	AfterUpdate(tx *gorm.DB) error
	BeforeDelete(tx *gorm.DB) error
	AfterDelete(tx *gorm.DB) error
	AfterFind(tx *gorm.DB) error

	GetRevision() int
	IncrementRevision()
}

Entity defines the interface for all database entities. It provides lifecycle hooks, soft delete support, optimistic locking, and standard accessor methods for common fields.

type EntityService

type EntityService[T Entity] interface {
	GeneralService
	GetZeroValue() T
	NewModelInstance() T
	GetEntityName() string
	GetDB(ctx context.Context) *gorm.DB
	GetReplicaDB(ctx context.Context) *gorm.DB
	GetSchema() (*schema.Schema, error)
	Create(ctx context.Context, entity T) error
	GetOrCreate(ctx context.Context, entity T) (T, error)
	Upsert(ctx context.Context, entity T, keyColumn string, keyValue any) error
	GetByID(ctx context.Context, id ID) (T, error)
	Update(ctx context.Context, entity T, fieldsToUpdate ...string) error
	Patch(ctx context.Context, id ID, data any) error
	Delete(ctx context.Context, id ID) error
	List(ctx context.Context, opts ...QueryOption) ([]T, error)
	Exec(ctx context.Context, sql string, values ...any) error
	ExecWithDB(db *gorm.DB, sql string, values ...any) error
	Query(ctx context.Context, opts ...QueryOption) ([]T, error)
	QueryFirst(ctx context.Context, opts ...QueryOption) (T, error)
	QueryWithDB(db *gorm.DB, opts ...QueryOption) ([]T, error)
	QueryFirstWithDB(db *gorm.DB, opts ...QueryOption) (T, error)
	Count(ctx context.Context, opts ...QueryOption) (int64, error)
	WithTransaction(ctx context.Context, fn func(txCtx context.Context, txDb *gorm.DB) error) error
	IsZeroValue(entity T) bool
	InvalidCache(ctx context.Context, id ID)
	UpdateCache(ctx context.Context, entity T)
	ValidateAccessibleAsUser(ctx context.Context, userID ID) error
	LogAuditEvent(ctx context.Context, entry *AuditLogEvent)
	NewAuditLogEvent(ctx context.Context, action string, id ID, data map[string]any) *AuditLogEvent
	NewAuditLogEventExtra(ctx context.Context, opts ...AuditLogEventOption) *AuditLogEvent
}

EntityService defines the generic service interface for CRUD operations on entities.

Usage:

service := NewEntityService[*YourEntityType](options)

// Create
entity := &YourEntityType{...}
err := service.Create(ctx, entity)

// Read
entity, err := service.GetByID(ctx, id)

// Update
entity.SomeField = newValue
err := service.Update(ctx, entity)

// Delete
err := service.Delete(ctx, id)

// Complex query
entities, err := service.Query(ctx,
    WithWhere("field = ?", value),
    WithOrder("created_at", true),
    WithPagination(1, 10),
)

// Transaction
err := service.WithTransaction(ctx, func(txCtx context.Context, txDb *gorm.DB) error {
    // ... multiple operations ...
    return nil
})

func NewEntityService

func NewEntityService[T Entity](options *ServiceOptions) EntityService[T]

NewEntityService creates a new EntityService for the given entity type. The options context map must contain OptionKeyDB and OptionKeyReplicaDB with *gorm.DB values.

type FilterScope

type FilterScope struct {
	Field      string       // struct field name
	QueryName  string       // query parameter name (from tag or JSON name)
	ColumnName string       // database column name (from tag or JSON name)
	Type       reflect.Type // field type for value parsing
}

FilterScope describes a filterable field extracted from struct tags.

func ParseFilterScopes

func ParseFilterScopes(t reflect.Type) []FilterScope

ParseFilterScopes parses all filter scopes from a struct type, including nested/embedded structs. It inspects ent struct tags to find fields with the "filter" scope and extracts query and column name mappings.

type GeneralService

type GeneralService interface {
	GetServiceName() string
	GetServiceOptions() *ServiceOptions
	GetTracer() Tracer
	GetCacheService() CacheService
	GetAuditService() AuditService
	GetContextExtractor() ContextExtractor
	GetLogger() *slog.Logger
}

GeneralService defines the common interface shared by all services, providing access to the logger, tracer, cache, audit, context extractor, and service options.

type GeneralServiceImpl

type GeneralServiceImpl struct {
	Logger *slog.Logger
	// contains filtered or unexported fields
}

GeneralServiceImpl provides a default implementation of GeneralService. Embed this in concrete service types to inherit logging, tracing, caching, and audit capabilities.

func NewGeneralService

func NewGeneralService(options *ServiceOptions, logger ...*slog.Logger) *GeneralServiceImpl

NewGeneralService creates a GeneralServiceImpl from the given options. Dependencies (tracer, cache, audit) are extracted from the options context map with safe fallbacks: NoopTracer, DummyCache, and DummyAuditService. An optional logger can be passed; otherwise slog.Default() is used.

func (*GeneralServiceImpl) GetAuditService

func (s *GeneralServiceImpl) GetAuditService() AuditService

GetAuditService returns the audit logging service.

func (*GeneralServiceImpl) GetCacheService

func (s *GeneralServiceImpl) GetCacheService() CacheService

GetCacheService returns the cache service.

func (*GeneralServiceImpl) GetContextExtractor

func (s *GeneralServiceImpl) GetContextExtractor() ContextExtractor

GetContextExtractor returns the context extractor used to resolve actor info.

func (*GeneralServiceImpl) GetLogger

func (s *GeneralServiceImpl) GetLogger() *slog.Logger

GetLogger returns the structured logger.

func (*GeneralServiceImpl) GetServiceName

func (s *GeneralServiceImpl) GetServiceName() string

GetServiceName returns the default service name.

func (*GeneralServiceImpl) GetServiceOptions

func (s *GeneralServiceImpl) GetServiceOptions() *ServiceOptions

GetServiceOptions returns the service options used to configure this service.

func (*GeneralServiceImpl) GetTracer

func (s *GeneralServiceImpl) GetTracer() Tracer

GetTracer returns the tracer used for distributed tracing.

type ID

type ID int64

ID is a snowflake-based unique identifier represented as a 64-bit integer. It serializes to JSON as a string to preserve precision in JavaScript clients.

func NewID

func NewID() ID

NewID generates a new unique snowflake ID. If the generator has not been initialized, it auto-initializes with node 0.

func ParseID

func ParseID(idAny any) (ID, error)

ParseID converts various types to an ID. Supported input types: ID, int, int64, string.

func (ID) Int64

func (id ID) Int64() int64

Int64 returns the ID as an int64.

func (ID) MarshalJSON

func (id ID) MarshalJSON() ([]byte, error)

MarshalJSON encodes the ID as a JSON string to avoid precision loss in JavaScript.

func (*ID) Scan

func (id *ID) Scan(src any) error

Scan implements sql.Scanner for reading ID values from database drivers.

func (ID) String

func (id ID) String() string

String returns the decimal string representation of the ID.

func (*ID) UnmarshalJSON

func (id *ID) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes the ID from either a JSON string or a JSON number.

func (ID) Value

func (id ID) Value() (driver.Value, error)

Value implements driver.Valuer for writing ID values to database drivers.

type InMemCache

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

InMemCache is a thread-safe in-memory cache with lazy TTL expiration. Expired entries are removed on read access.

func (*InMemCache) Delete

func (c *InMemCache) Delete(key string) error

Delete removes a value by key.

func (*InMemCache) Get

func (c *InMemCache) Get(key string) (string, error)

Get retrieves a value by key. If the entry has expired, it is lazily deleted and ErrCacheMiss is returned.

func (*InMemCache) Set

func (c *InMemCache) Set(key string, value string) error

Set stores a value with the default expiration (5 minutes).

func (*InMemCache) SetWithExp

func (c *InMemCache) SetWithExp(key string, value string, exp time.Duration) error

SetWithExp stores a value with a specific expiration duration.

type M

type M = map[string]any

M is a convenient alias for map[string]any, commonly used for passing dynamic key-value data such as GORM update maps or JSON payloads.

type NoopTracer

type NoopTracer struct{}

NoopTracer is a tracer that produces no-operation spans. It is useful as a default when no tracing backend is configured.

func (NoopTracer) StartSpan

func (NoopTracer) StartSpan(ctx context.Context, operation string) Span

StartSpan returns a no-operation span.

type Operator

type Operator string

Operator represents logical operators used to combine conditions.

const (
	AND Operator = "AND"
	OR  Operator = "OR"
)

type OptionKey

type OptionKey string

OptionKey identifies a named entry in the ServiceOptions context map.

const (
	OptionKeyServiceContext   OptionKey = "service_context"
	OptionKeyAudit            OptionKey = "audit"
	OptionKeyCache            OptionKey = "cache"
	OptionKeyDB               OptionKey = "db"
	OptionKeyReplicaDB        OptionKey = "replica_db"
	OptionKeyRedis            OptionKey = "redis"
	OptionKeyTracer           OptionKey = "tracer"
	OptionKeyContextExtractor OptionKey = "context_extractor"
)

type QueryMap

type QueryMap map[string][]string

QueryMap is a multi-valued map typically populated from URL query parameters. Keys are parameter names and values are slices of strings supporting multi-value parameters.

func (QueryMap) Filter

func (m QueryMap) Filter(columns []string) QueryMap

Filter removes all keys from the QueryMap that are not present in the provided columns slice. This is useful for restricting filter parameters to a known set of allowed columns.

func (*QueryMap) Merge

func (m *QueryMap) Merge(params map[string]any) QueryMap

Merge merges params into the QueryMap. If a param value is an array/slice, all values are appended. Otherwise, the value is converted to string and appended as a single value.

func (QueryMap) ToMap

func (m QueryMap) ToMap() map[string]any

ToMap converts QueryMap to map[string]any. Single values are kept as strings. Multiple values are converted to "in:value1,value2" format.

type QueryOption

type QueryOption func(*gorm.DB) *gorm.DB

QueryOption is a function that modifies a GORM query. It is the primary building block for composing query behavior (pagination, filtering, ordering, etc.).

func WithConditionBuilder

func WithConditionBuilder(qb *ConditionBuilder) QueryOption

WithConditionBuilder creates a QueryOption from a ConditionBuilder. If the builder has no conditions, the query is returned unchanged.

func WithFilter

func WithFilter(filters map[string]any) QueryOption

WithFilter creates a QueryOption from a filter map. Each key is a column name, and the value is processed through processFilter to support operator prefixes (like:, gt:, in:, etc.). Common pagination and sorting parameters are automatically ignored.

func WithFilterFrom

func WithFilterFrom[T any](queryMap QueryMap) QueryOption

WithFilterFrom creates a QueryOption by parsing filter scopes from the type parameter T and building filters from the provided query map.

func WithOffsetLimit

func WithOffsetLimit(offset, limit int) QueryOption

WithOffsetLimit creates a QueryOption that applies raw offset/limit pagination.

func WithOrder

func WithOrder(field string, desc bool) QueryOption

WithOrder creates a QueryOption that orders by a single field. When desc is true, " DESC" is appended to the field name.

func WithOrderFrom

func WithOrderFrom(queryMap QueryMap) QueryOption

WithOrderFrom creates a QueryOption for ordering based on query parameters. Supports multiple order params with format: field:desc or field:asc. Example: ?order=created_at:desc,id:desc Falls back to "id DESC" when no order parameters are provided.

func WithPagination

func WithPagination(page, size int) QueryOption

WithPagination creates a QueryOption that applies page-based pagination. Pages are 1-indexed: page=1 returns the first `size` records.

func WithPaginationFrom

func WithPaginationFrom(queryMap QueryMap) QueryOption

WithPaginationFrom creates a pagination QueryOption from query parameters. Supports "page" and "size" parameters (e.g., ?page=1&size=10). Defaults to page=1, size=DefaultPageSize. Size is capped at MaxPageSize.

func WithWhere

func WithWhere(condition string, args ...any) QueryOption

WithWhere creates a QueryOption that adds a WHERE condition with explicit condition string and arguments.

func WithWhereArgs

func WithWhereArgs(args ...any) QueryOption

WithWhereArgs creates a QueryOption that adds a WHERE condition using variadic arguments. The first argument is the condition (string or struct), and the rest are bind parameters.

type SQLBuilder

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

SQLBuilder provides a fluent API for constructing complex SQL queries with support for CTEs, DISTINCT ON, subqueries, and filter application. Arguments are stored per-section and concatenated in SQL clause order by Build().

func NewSQLBuilder

func NewSQLBuilder() *SQLBuilder

NewSQLBuilder creates a new SQLBuilder instance with pre-allocated slices.

func (*SQLBuilder) ApplyFilter

func (b *SQLBuilder) ApplyFilter(filters map[string]any) *SQLBuilder

ApplyFilter applies a filter map to the query builder. Each entry is processed through processFilter to support operator prefixes.

func (*SQLBuilder) Build

func (b *SQLBuilder) Build() (string, []any)

Build generates the final SQL query string and its arguments. Arguments are collected in SQL clause order: WITH, SELECT, FROM, WHERE.

func (*SQLBuilder) DistinctOn

func (b *SQLBuilder) DistinctOn(columns ...string) *SQLBuilder

DistinctOn adds DISTINCT ON columns to the query (PostgreSQL-specific).

func (*SQLBuilder) From

func (b *SQLBuilder) From(fromPart string, args ...any) *SQLBuilder

From sets the FROM part of the query with optional arguments for subqueries or parameterized table expressions.

func (*SQLBuilder) GroupBy

func (b *SQLBuilder) GroupBy(columns ...string) *SQLBuilder

GroupBy adds columns to the GROUP BY clause.

func (*SQLBuilder) OrderBy

func (b *SQLBuilder) OrderBy(columns ...string) *SQLBuilder

OrderBy adds columns to the ORDER BY clause.

func (*SQLBuilder) Select

func (b *SQLBuilder) Select(selectPart string, args ...any) *SQLBuilder

Select sets the SELECT part of the query with optional arguments for parameterized expressions.

func (*SQLBuilder) Where

func (b *SQLBuilder) Where(condition string, args ...any) *SQLBuilder

Where adds a WHERE condition with arguments. Multiple calls produce conditions joined with AND.

func (*SQLBuilder) With

func (b *SQLBuilder) With(name string, query string, args ...any) *SQLBuilder

With adds a CTE (Common Table Expression) with a raw query string and optional arguments.

func (*SQLBuilder) WithBuilder

func (b *SQLBuilder) WithBuilder(name string, builder *SQLBuilder) *SQLBuilder

WithBuilder adds a CTE whose query is built from another SQLBuilder.

type ScopeAttribute

type ScopeAttribute struct {
	Name  string
	Value string
}

ScopeAttribute represents a parsed attribute from an ent scope tag.

Example:

type User struct {
    entigo.BaseEntity
    Name   string `json:"name" ent:"scope=update(required,max=100),sort(default=asc)"`
    Email  string `json:"email" ent:"scope=update(readonly,format=email)"`
    Status string `json:"status" ent:"scope=update(readonly,default=active),filter(name=status)"`
    Score  int    `json:"score" ent:"scope=update(min=0,max=100)"`
}

func GetScopeAndAttributes

func GetScopeAndAttributes(tag, scopeName string) (bool, []ScopeAttribute)

GetScopeAndAttributes returns whether the given tag contains the specified scope, and if so, returns its parsed attributes.

func ParseScopeAttributes

func ParseScopeAttributes(scopeStr string) []ScopeAttribute

ParseScopeAttributes parses the attributes within a scope's parentheses.

Valueless attribute:

scope=update(readonly)
scope=update(required,readonly)

Valued attribute:

scope=update(max=100)
scope=update(type=json,max=100)

Mixed attributes:

scope=update(readonly,max=100,min=0)
scope=update(required,type=string,max=100)

type ServiceOptions

type ServiceOptions struct {
	DebugMode    bool
	DebugSQLMode bool
	Context      map[OptionKey]any
}

ServiceOptions holds configuration for entity services, including debug flags and a key-value context map for injecting dependencies like DB connections, cache, tracer, and audit service.

func NewServiceOptions

func NewServiceOptions() *ServiceOptions

NewServiceOptions creates a new ServiceOptions with an empty context map.

func (*ServiceOptions) Clone

func (s *ServiceOptions) Clone() *ServiceOptions

Clone creates a deep copy of the ServiceOptions, including the context map.

func (*ServiceOptions) Get

func (s *ServiceOptions) Get(key OptionKey) any

Get retrieves the value for the given key from the context map.

func (*ServiceOptions) With

func (s *ServiceOptions) With(key OptionKey, value any) *ServiceOptions

With sets a key-value pair in the context map and returns the receiver for method chaining.

type Span

type Span interface {
	Finish()
}

Span represents a unit of work that can be finished to record its duration.

type Tracer

type Tracer interface {
	StartSpan(ctx context.Context, operation string) Span
}

Tracer creates spans for tracking operations. Implementations can integrate with distributed tracing systems.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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